Skip to content

Logging and speech to text refactoring - #18

Open
vinniefalco wants to merge 76 commits into
cppalliance:masterfrom
vinniefalco:master
Open

Logging and speech to text refactoring#18
vinniefalco wants to merge 76 commits into
cppalliance:masterfrom
vinniefalco:master

Conversation

@vinniefalco

Copy link
Copy Markdown
Member

In the commit log and plans

Move asset and gateway-config route tests into child modules so the production modules stay below their ratchet ceilings without changing behavior. Record the measured parent and test-module sizes in `module-ceilings.toml`.
The gateway command line has one job, to serve, so the `serve` subcommand and the positional config argument come out: the bare invocation serves with boot discovery, and `--config PATH` names an explicit config, winning over `PROMPTFORGE_GATEWAY_CONFIG`. `parse_args` drops its subcommand match and rejects any non-flag argument as a usage error; every repository-owned caller - the systemd unit, the installer, the Workshop launcher, the tray autostart entries, the release-test workflow, and the guides - moves to the new shape in the same change.

- `init_logging` now runs after the already-running handoff check, so `--help`, `--version`, and a second-instance handoff never rotate the running gateway's log; the handoff's browser-open failure reports through `eprintln!` because no subscriber is installed yet.
- `--version` is accepted at any position in the argument list, and a second `--config` is a usage error.
- On the handoff path in `relaunch.rs`, the connection-file resolution warning is dropped with no subscriber installed; the boot that follows logs its own failure once logging is live.

Plan: 2026-09-05-1-gateway-logging-cli
Move the log pipeline out of `crates/gateway/src/main.rs` into a new `gateway-logging` crate so the queue, rotation, sink, and worker lifecycle are owned and tested in one place. The crate exports `LogConfig`, `LogRuntime`, `LogWriter`, and the opaque `LogError`; `LogRuntime::start` rotates and opens `gateway.log`, spawns one worker thread, and `shutdown` closes admission, drains, flushes, and joins. `main.rs` keeps global subscriber installation, holds the returned `LogRuntime`, and shuts the logger down last so fatal error chains logged through `log_error_chain` reach the disk.

- Queue policy is fixed in `queue.rs`: `CAPACITY` of 8192 records, drain `BATCH` of 256, one deque per `LogPriority` under one mutex. A full queue evicts the oldest Debug, then Trace, then Info; Warn and Error records are never evicted, and a producer with no eligible record blocks on a condition variable.
- `LogEventWriter` buffers every `Write` call for one event and enqueues on `Drop`, moving the buffer through `String::from_utf8` and paying the lossy copy only for invalid UTF-8. It is public but `#[doc(hidden)]` because `MakeWriter::Writer` cannot name a private type.
- A failed write or flush on the file sink falls back to synchronous stderr, and a worker panic surfaces from `shutdown` as a `LogError` that `is_io` classifies separately from filesystem and spawn failures.
- Rotation keeps one previous run: an existing `gateway.log` renames to `gateway.log.1`, overwriting the older rotation.

Plan: 2026-09-05-1-gateway-logging-cli
A failed gateway run must be discoverable without config knowledge, and no log record may carry secret material. The gateway gains a `diagnostics` subcommand that prints a read-only JSON report of the state dir, config, logs, and connection file, the log rotation retains five previous runs, and every queued record crosses a redaction pass that masks bearer tokens, authorization and cookie header values, and `api_key` assignments.

- The log layout gets one owner: `LogConfig::log_path` and `LogConfig::retained_log_paths` name every path, so `diagnostics_json` enumerates the logs without starting a runtime and `open_log_file` rotates the same chain.
- Redaction sits at the one chokepoint every record crosses: `LogEventWriter::drop` masks the formatted line with `redact_line` before the record enters the queue.
- `is_running` in `shared-sidecar` is read-only: a stale or corrupt connection file reads as not-running and stays on disk for the next launch to clean.
- `discover_in(explicit, gather)` splits the report's config discovery into a testable inner in the `resolve_in` pattern; unit tests pin the explicit, discovered, profile-fallback, and gather-failure branches, and both `diagnostics` integration tests assert `config.path` and `config.exists`.
- `diagnostics` runs before the handoff check and before logging starts; it never serves, rotates a log, parses a config, or mutates the state directory, and `parse_diagnostics_args` accepts only `--config PATH`.
- The generated config carries `# Diagnostics: promptforge-gateway diagnostics` as a comment, so the file stays parseable.
- New tests pin the sink's stderr fallback on rejected writes and flushes, saturation that never evicts or duplicates Warn or Error records, and a shutdown that writes every record in enqueue order before the join returns.
- `Sink::Null` and `Sink::is_stderr` are `#[cfg(test)]` seams for the fallback and latency tests.
- `production_logging_stays_within_latency_budget` is `#[ignore]`d; it runs only through `cargo test -p gateway-logging --release -- --ignored`.

Plan: 2026-09-05-1-gateway-logging-cli
The public `serve` docs linked the private `GRACEFUL_DRAIN_TIMEOUT` and `WORKER_JOIN_TIMEOUT` constants, which `RUSTDOCFLAGS="-D warnings" cargo doc` rejects as private intra-doc links. The constants are now plain backticked names. The break was introduced in 7f24bb0 and predates the logging work.
The logging contract now lives in the documentation, and the dependency boundary has a test that enforces it. A new integration test `the_manifest_declares_only_the_tracing_dependencies` reads the crate's own `Cargo.toml` and fails when any dependency other than `tracing` and `tracing-subscriber` appears. The `gateway-logging` `AGENTS.md`, the gateway `README.md`, and both gateway guides now describe the `gateway.log` rotation, the five-run retention, the redaction pass, and the `promptforge-gateway diagnostics` report.

- The boundary test rejects build, dev, and target-specific dependency tables in addition to extra `[dependencies]` entries, so the allowlist covers every way a crate can enter the build. It parses the manifest line by line and adds no TOML parser dependency.
- `AGENTS.md` records that `LogEventWriter` is public but `#[doc(hidden)]` because `MakeWriter::Writer` cannot name a private type, and that the test seams `Sink::Null` and `Sink::is_stderr` exist only under `cfg(test)`.

Plan: 2026-09-05-1-gateway-logging-cli
Plan: 2026-09-05-1-gateway-logging-cli
Characterize current batch routing and realtime transcription before the speech subsystem changes. Separate physical-model routing checks from legacy socket cases, and add deterministic coverage for stream policy, generation, origin, ordering, shutdown, and final-model authority.

- `crates/gateway-stt/tests/it/main.rs` now separates batch model selection from legacy socket characterization.
- `fixture_runtime_with_models` starts caller-selected interim and final fixture models on a dedicated thread, while `TestServer::shutdown` stops the server before blocking runtime shutdown.
- `batch_selects_each_loaded_physical_model_by_name` changes one vocabulary token to verify direct routing to each loaded physical model.
- `final_model_segments_and_tail_are_authoritative_at_stop` verifies that interim text stays provisional and that the final worker produces committed segments and the remaining tail.
- `crates/gateway-stt/tests/it/batch.rs` keeps its physical-model case ignored because it requires `tests/fixtures/`. Other native speech cases remain ignored for the same reason.

Design: extends oversized-unit @ crates/gateway-stt/tests/common/mod.rs
Design: new flag-parameter @ crates/gateway-stt/tests/common/mod.rs::fixture_runtime deps: bool
Design: replaces flag-parameter @ crates/gateway-stt/tests/common/mod.rs::fixture_server deps: bool was: crates/gateway-stt/tests/it/stt.rs::fixture_server
Design: new stringly-typed @ crates/gateway-stt/tests/common/mod.rs::multipart_body deps: &[u8],&str boundary: wire
Design: new stringly-typed @ crates/gateway-stt/tests/common/mod.rs::transcribe_batch deps: &[f32],&str,SttState boundary: wire
Design: replaces oversized-unit @ crates/gateway-stt/tests/it/legacy_stream.rs was: crates/gateway-stt/tests/it/stt.rs
Design: new pure-function @ crates/gateway-stt/tests/it/legacy_stream.rs::transcript_words deps: &str
Design: new pure-function @ crates/gateway-stt/tests/it/legacy_stream.rs::distinguishing_word deps: &str,&str
Deferred: physical-model characterizations remain ignored without whisper fixtures
Plan: 2026-09-05-2-generic-realtime-stt
Add an ignored native integration target that loads the packaged runtime and exact model fixture. It fixes interim, final, transcript-conditioning, glossary-prompt, silence-gating, and cleanup behavior so the upcoming engine split can be checked against one baseline.

- `packaged_runtime_preserves_native_transcription_contract` copies the exact tiny model into a temporary directory, loads the packaged library, and configures the same model for interim and final decoding.
- `JFK_TRANSCRIPT` anchors assertions for the full interim transcription, unprompted and transcript-conditioned final output, glossary bias, silence gating, and conditioning divergence.
- `std::fs::remove_file` verifies that dropping both engines releases the copied model.
- `#[ignore = "requires whisper test fixtures (tests/fixtures/)"]` keeps native characterization outside default test runs because it requires packaged runtime fixtures.

Design: new oversized-unit @ crates/gateway-transcribe/tests/native_whisper.rs::packaged_runtime_preserves_native_transcription_contract
Plan: 2026-09-05-2-generic-realtime-stt
Add canonical client, server, session, error, and sequence fixtures for realtime transcription. Validate one shared fixture set in Rust and the Workshop UI to pin strict fields, event ordering, capacity errors, item isolation, and hypothesis semantics before the subsystem changes.

- `realtime-wire-fixtures.mjs` consumes the Gateway-owned fixtures directly, which makes Rust and Workshop share one canonical corpus.
- `canonical_realtime_events_are_complete_strict_and_round_trip` enforces exact case sets, strict field sets, identifier separation, session defaults, and hypothesis composition.
- `canonical_realtime_sequences_cover_valid_and_invalid_contract_paths` validates event order, error correlation, minimum audio, and recovery metadata across all declared sequences.
- `crates/gateway-stt/tests/it/realtime_fixtures.rs` does not call production realtime parsers or session handlers, so these tests pin fixture consistency rather than implementation conformance.

Design: new oversized-unit @ crates/gateway-stt/tests/it/realtime_fixtures.rs::assert_server_event_fields deps: &Value,&str
Design: new oversized-unit @ crates/gateway-stt/tests/it/realtime_fixtures.rs::canonical_realtime_events_are_complete_strict_and_round_trip
Design: new oversized-unit @ crates/gateway-stt/tests/it/realtime_fixtures.rs::canonical_realtime_sequences_cover_valid_and_invalid_contract_paths
Violates: A2 - not determinable from diff
Violates: A96 - not determinable from diff
Deferred: batch transcription characterization is absent
Deferred: native two-model characterization is absent
Plan: 2026-09-05-2-generic-realtime-stt
Give the backend-neutral engine its intended role-specific identity. Update workspace metadata, runtime references, tests, fixture exclusions, and documentation while preserving the engine implementation.

- `crates/gateway-stt-engine` changes the package, path, and Rust import identity.
- `crates/gateway-stt-engine/src/engine.rs` and the other engine source files move with 100 percent similarity.
- `crates/gateway-stt-engine/tests/native_whisper.rs` changes only its import path. The commit adds no test assertions or compatibility crate.

Design: new shotgun-surgery @ crates/gateway-stt-engine/Cargo.toml
Violates: A2 - not determinable from diff
Violates: A96 - not determinable from diff
Plan: 2026-09-05-2-generic-realtime-stt
Move per-take speech state out of decode workers so each decode job is independent and every take owns its lifecycle. Carry immutable guidance and finalized history with each job, aggregate segment results in one ordered pipeline, and preserve interim fallback after failures.

- `Take` consolidates guidance, finalized history, segmentation, local agreement, transcript aggregation, completion, and failure behind one per-take state object.
- `FinalJob` carries all decode inputs and the reply channel, so final-model workers retain no take identity or transcript between jobs.
- `Segmenter` moves from the engine surface to the gateway speech surface with take orchestration.
- `run_final_pipeline` serializes closed segments and the closing tail, records only successful sample boundaries, and stops decode work after the first failure.
- `next_interim` promotes token prefixes confirmed by two hypotheses and keeps committed text append-only.
- `TestServer` now bounds fixture server and runtime cleanup at 30 seconds.
- `guidance` has no end-to-end assertion from runtime activation through both batch and streaming decode paths.

Design: new surface-growth @ crates/gateway-stt-engine/src/engine.rs::SttEngine::transcribe boundary: pub
Design: new surface-growth @ crates/gateway-stt-engine/src/engine.rs::SttEngine::transcribe_final boundary: pub
Design: new shared-parameter-cluster @ crates/gateway-stt-engine/src/engine.rs::SttEngine::transcribe_final boundary: pub
Design: new shared-parameter-cluster @ crates/gateway-stt-engine/src/final_pass.rs::FinalTranscriber::transcribe
Design: new surface-growth @ crates/gateway-stt/src/lib.rs::Segmenter boundary: pub
Design: new value-object @ crates/gateway-stt/src/take.rs::AgreementSnapshot
Design: new parameter-object @ crates/gateway-stt/src/take.rs::Take
Design: new shared-mutable-state @ crates/gateway-stt/src/take.rs::TakeState
Design: new message-passing @ crates/gateway-stt/src/take.rs::FinalPipeline
Design: new pure-function @ crates/gateway-stt/src/take.rs::matching_token_prefix_end deps: &str,&str
Design: new pure-function @ crates/gateway-stt/src/take.rs::token_spans deps: &str
Design: new pure-function @ crates/gateway-stt/src/take.rs::after_token_prefix deps: &str,usize
Design: new oversized-unit @ crates/gateway-stt/src/take.rs
Design: extends oversized-unit @ crates/gateway-stt/tests/common/mod.rs
Design: extends oversized-unit @ crates/gateway-stt/tests/it/legacy_stream.rs
Violates: A2 - not determinable from diff
Violates: A96 - not determinable from diff
Deferred: configured guidance propagation lacks an end-to-end assertion
Plan: 2026-09-05-2-generic-realtime-stt
Make speech decoding backend-neutral while keeping Whisper construction, prompting, progress, and error translation in a safe adapter. Inject model factories into dedicated workers so model creation and decoding stay on their owning threads. Preserve batch and native transcription behavior through relocated and expanded tests.

- `Decoder` and `ModelFactory` establish backend strategy contracts, while `WhisperModelFactory` contains safe model construction and decode policy.
- `SttEngine::new` constructs each decoder on its owning worker and returns initialization errors before activation.
- `native_whisper.rs` relocates native characterization and adds isolation, optional-final, and load-progress checks. These fixture-dependent tests remain ignored.
- `std::sync::mpsc::channel` leaves both worker job queues unbounded.
- `require_fixture` duplicates native fixture loading across unit and integration test support.

Design: new strategy @ crates/gateway-stt-engine/src/decoder.rs::Decoder boundary: pub
Design: new surface-growth @ crates/gateway-stt-engine/src/decoder.rs::Decoder boundary: pub
Design: new strategy @ crates/gateway-stt-engine/src/decoder.rs::ModelFactory boundary: pub
Design: new surface-growth @ crates/gateway-stt-engine/src/decoder.rs::ModelFactory boundary: pub
Design: new facade @ crates/gateway-stt-backend-whisper/src/lib.rs boundary: pub
Design: new constructor-injection @ crates/gateway-stt-engine/src/engine.rs::SttEngine::new
Design: new flag-parameter @ crates/gateway-stt-backend-whisper/src/model.rs::WhisperDecoder::load
Design: new flag-parameter @ crates/gateway-stt-engine/src/worker.rs::worker_loop deps: &dyn ModelFactory,&std::sync::mpsc::Receiver<Job>,&std::sync::mpsc::SyncSender<Result<bool, TranscribeError>>,bool
Design: replaces shared-mutable-state @ crates/gateway-stt/src/runtime.rs::SttSlot was: crates/gateway-stt-engine/src/slot.rs::SttSlot
Design: new global-state @ crates/gateway-stt-backend-whisper/src/prompt.rs::NATIVE_TEST
Design: new global-state @ crates/gateway-stt-backend-whisper/tests/native_whisper.rs::NATIVE_TEST
Design: new clone-block @ crates/gateway-stt/src/test_fixtures.rs
Design: new clone-block @ crates/gateway-stt/tests/common/mod.rs
Violates: A2 - crates/gateway-stt/src/runtime.rs is not determinable from diff
Pending: N9 - compounds
Deferred: model worker queues remain unbounded
Deferred: native Whisper characterization remains ignored behind external fixtures
Plan: 2026-09-05-2-generic-realtime-stt
Add mandatory checks for workspace edges, module cycles, public exports, source size, migration targets, and unsafe isolation. Split compiler-resolved checks from strict policy checks and run both in the normal continuous integration path.

- `parseCargoModulesDot` is a 110-line parser that collapses item edges into module edges and rejects malformed graph output.
- `module-ceilings.toml` files set strict source and public-root ceilings and name each planned migration target.
- `architecture` runs with pinned tool versions in the normal continuous integration job.

Design: new global-state @ crates/gateway-stt/tests/it/architecture.rs::workspace_metadata
Design: new oversized-unit @ tools/check-stt-architecture.mjs::parseCargoModulesDot deps: output
Violates: A2 - not determinable from diff
Plan: vibe/2026-09-05-2-generic-realtime-stt.md
Bounded queues now reject excess speech jobs, contain worker panics, and join decoding threads during shutdown. Feature-gated scripted decoders provide deterministic downstream and route tests without a production constructor.

- `Transcriber` replaces unbounded submission with fixed-capacity admission and owns a shared stop flag that closes admission before its thread joins.
- `test_fixtures` exposes scripted factory and decoder controls only when consumers enable the fixture feature.
- `TranscribeError::Overloaded` reports a full queue without waiting, and `TranscribeError::WorkerPanicked` separates panics from a disconnected worker.
- `scripted_workers_can_be_injected_without_a_production_constructor` sends a multipart request through the router and checks the scripted transcript and decoded samples.

Design: new feature-flag @ crates/gateway-stt-backend-whisper/Cargo.toml::test-fixtures
Design: new feature-flag @ crates/gateway-stt-engine/Cargo.toml::test-fixtures
Design: new surface-growth @ crates/gateway-stt-engine/src/engine.rs::SttEngine::shutdown boundary: pub
Design: new surface-growth @ crates/gateway-stt-engine/src/error.rs::TranscribeError boundary: pub
Design: new surface-growth @ crates/gateway-stt-engine/src/lib.rs::test_fixtures boundary: pub
Design: new shared-mutable-state @ crates/gateway-stt-engine/src/test_fixtures.rs::ScriptedDecoder
Design: new temporal-coupling @ crates/gateway-stt-engine/src/test_fixtures.rs::ScriptedDecoder
Design: new shared-mutable-state @ crates/gateway-stt-engine/src/worker.rs::Transcriber
Design: extends flag-parameter @ crates/gateway-stt-engine/src/worker.rs::worker_loop deps: &AtomicBool,&dyn ModelFactory,&mpsc::Receiver<Job>,&mpsc::SyncSender<Result<bool, TranscribeError>>,bool
Design: new feature-flag @ crates/gateway-stt/Cargo.toml::test-fixtures
Design: new surface-growth @ crates/gateway-stt/src/lib.rs::test_fixtures boundary: pub
Design: new facade @ crates/gateway-stt/src/lib.rs::test_fixtures
Design: new constructor-injection @ crates/gateway-stt/src/runtime.rs::SttRuntime::from_scripted_engine
Plan: vibe/2026-09-05-2-generic-realtime-stt.md
Run backend-neutral ownership and bounded queue tests under a pinned interpreter. Make decode jobs and engine policy explicit values, bound worker startup and cleanup, then place the approved serving-log correction before final release verification.

- `DecodeRequest` replaces separate mode, sample, guidance, and finalized-history arguments with one owned job. `EnginePolicy` owns validated capture settings, the hardware capability fact, and the shared startup timeout.
- `vibe/2026-09-05-2-generic-realtime-stt.md` records completed markers through the interpreter work, replaces commit hashes in completed headings, and inserts serving-log bookends before full release verification.
- `SttEngine::new` starts interim and final construction under one absolute deadline. It aggregates role failures, preserves partial-cleanup failures, and explicitly abandons only non-preemptible timed-out startup handles.
- `SttEngine::shutdown` joins every ordinary worker and returns one or multiple panic outcomes. Repeated calls preserve the observed failures.
- `.github/workflows/stt-miri.yml` pins pure ownership and queue checks to the selected toolchain and adds native checks with hash-verified runtime, model, and audio fixtures.
- `ScriptedDecoder` adds construction rendezvous and drop-panic controls to shared fixture state. Tests cover exact queue boundaries, cancellation, startup deadlines, failure aggregation, cleanup, thread confinement, and shutdown.
- `.github/workflows/stt-miri.yml` keeps sockets, dynamic FFI, native callbacks, and model loading outside the interpreter and assigns them to native CI.

Design: new parameter-object @ crates/gateway-stt-engine/src/decoder.rs::DecodeRequest boundary: pub
Design: new encapsulated-invariant @ crates/gateway-stt-engine/src/policy.rs::EnginePolicy boundary: pub
Design: removes shared-parameter-cluster @ crates/gateway-stt-engine/src/engine.rs::SttEngine::transcribe_final
Design: flag-parameter -> dispatch-on-tag @ crates/gateway-stt-engine/src/worker.rs::worker_loop deps: &AtomicBool,&dyn ModelFactory,&mpsc::Receiver<Job>,&mpsc::SyncSender<Result<bool, TranscribeError>>,DecodeMode
Design: new dispatch-on-tag @ crates/gateway-stt-engine/src/engine.rs::SttEngine::decode boundary: pub
Design: extends shared-mutable-state @ crates/gateway-stt-engine/src/test_fixtures.rs::ScriptedDecoder
Design: extends temporal-coupling @ crates/gateway-stt-engine/src/test_fixtures.rs::ScriptedDecoder
Violates: A2 - credential ownership in crates/gateway-stt/src/runtime.rs is not determinable from diff
Violates: A96 - browser content bounds in crates/gateway-stt/src/api.rs are not determinable from diff
Violates: A115 - control readiness during crates/gateway-stt/src/runtime.rs model startup is not determinable from diff
Pending: N21 - compounds
Pending: N22 - compounds
Plan: vibe/2026-09-05-2-generic-realtime-stt.md
Move speech pipeline tuning to a canonical top-level configuration while preserving legacy input during parsing. Reject mixed canonical and legacy forms, serialize only the canonical form, and apply tuning changes without a restart.

- `SttPipelineConfig` keeps validated window, interval, and vocabulary state private and exposes read-only access. The `stt` accessor replaces speech tuning access through `WorkshopConfig`.
- `migrate_legacy_stt` accepts legacy input only when canonical input is absent, and `canonicalizeStt` prevents browser saves from writing the legacy shape. Validation tests cover zero bounds, conflicting forms, serialization, hot apply, and UI persistence.

Design: new value-object @ crates/gateway-config/src/config/stt.rs::SttPipelineConfig boundary: pub
Design: new encapsulated-invariant @ crates/gateway-config/src/config/stt.rs::SttPipelineConfig boundary: pub
Design: new surface-growth @ crates/gateway-config/src/config/accessors.rs::Config::stt boundary: pub
Design: new shim @ crates/gateway-config/src/config/imp.rs::migrate_legacy_stt deps: &mut toml::Value boundary: persisted
Design: new stringly-typed @ crates/gateway-config/src/config/imp.rs::migrate_legacy_stt deps: &mut toml::Value boundary: persisted
Design: new shim @ crates/gateway-config-ui/ui/src/services/config-store.ts::canonicalizeStt deps: EntryData boundary: persisted
Design: new stringly-typed @ crates/gateway-config-ui/ui/src/services/config-store.ts::canonicalizeStt deps: EntryData boundary: persisted
Violates: A2 - credential ownership in SttRuntime is not determinable from diff
Violates: A96 - bounded third-party model content in canonicalizeStt is not determinable from diff
Violates: A115 - control readiness in SttRuntime is not determinable from diff
Violates: A116 - publication consistency in stt_pipeline_change_reloads_without_restart is not determinable from diff
Pending: N9 - compounds
Pending: N27 - compounds
Deferred: gateway configuration test module registration is absent
Deferred: generated guide summary and index updates are absent
Plan: vibe/2026-09-05-2-generic-realtime-stt.md
Decode canonical Base64 PCM audio into one continuous resampled stream for realtime transcription. Enforce append size, buffered duration, sample integrity, and minimum commit limits before downstream decoding.

- `AudioBuffer` owns odd-byte carry, input duration, and the `Resampler24To16` timeline. A successful `commit` flushes the final output position and resets all ingestion state.
- `decode_base64` rejects malformed, noncanonical, and oversized input. `pcm16le-24khz.json` pins language-neutral little-endian bytes for other consumers.
- `audio` remains private under `allow(dead_code)` and has no runtime caller in the touched files.

Design: new oversized-unit @ crates/gateway-stt/src/audio.rs
Design: new pure-function @ crates/gateway-stt/src/audio.rs::decode_base64 deps: str
Design: new value-object @ crates/gateway-stt/src/audio.rs::CommittedAudio
Violates: A2 - crates/gateway-stt/src/audio.rs does not determine credential ownership
Pending: N6 - compounds
Pending: N9 - compounds
Deferred: Realtime session wiring remains absent from this commit
Plan: vibe/2026-09-05-2-generic-realtime-stt.md
Define private client and server events for realtime transcription. Reject unsupported fields and invalid shapes before state changes, preserve atomic session updates, and issue independent opaque identifiers. Record CI repairs and align migration deadlines with the expanded schedule.

- `ClientEvent` and `ServerEvent` encode the accepted event families as crate-private types with strict shape checks.
- `NEXT_GENERATOR` allocates generator namespaces from process-wide atomic state, and `parse_empty` selects commit or clear behavior through a Boolean parameter.
- `apply_update_text` clones the effective session and publishes only a fully valid update. `validate` accepts only the exact transcription query.
- `canonical_client_events_parse_and_updates_are_atomic` and `canonical_server_events_round_trip_with_exact_shapes` pin atomic updates and exact fixture parity.
- `realtime` remains private and intentionally unwired to a socket route.

Design: new pure-function @ crates/gateway-stt/src/realtime/query.rs::validate deps: Option
Design: new dispatch-on-tag @ crates/gateway-stt/src/realtime/wire/client.rs::parse_client_event deps: str
Design: new pure-function @ crates/gateway-stt/src/realtime/wire/client.rs::parse_client_event deps: str
Design: new pure-function @ crates/gateway-stt/src/realtime/wire/client.rs::correlation deps: Map
Design: new pure-function @ crates/gateway-stt/src/realtime/wire/client.rs::required_string deps: Correlation,Map,str,str
Design: new pure-function @ crates/gateway-stt/src/realtime/wire/client.rs::reject_unknown deps: Correlation,Map,str,str
Design: new pure-function @ crates/gateway-stt/src/realtime/wire/client.rs::object_at deps: Correlation,Value,str
Design: new pure-function @ crates/gateway-stt/src/realtime/wire/client.rs::parse_append deps: Correlation,Map
Design: new flag-parameter @ crates/gateway-stt/src/realtime/wire/client.rs::parse_empty deps: Correlation,Map,bool
Design: new pure-function @ crates/gateway-stt/src/realtime/wire/client.rs::parse_empty deps: Correlation,Map,bool
Design: new pure-function @ crates/gateway-stt/src/realtime/wire/client.rs::client_id deps: Correlation
Design: new pure-function @ crates/gateway-stt/src/realtime/wire/client.rs::parse_update deps: Correlation,Map
Design: new pure-function @ crates/gateway-stt/src/realtime/wire/client.rs::parse_audio deps: Correlation,Value
Design: new pure-function @ crates/gateway-stt/src/realtime/wire/client.rs::parse_format deps: Correlation,Value
Design: new pure-function @ crates/gateway-stt/src/realtime/wire/client.rs::parse_transcription deps: Correlation,Value
Design: new pure-function @ crates/gateway-stt/src/realtime/wire/client.rs::parse_include deps: Correlation,Value
Design: new stringly-typed @ crates/gateway-stt/src/realtime/wire/shared.rs::ClientEvent
Design: new pure-function @ crates/gateway-stt/src/realtime/wire/shared.rs::deserialize_required_nullable deps: D
Design: new global-state @ crates/gateway-stt/src/realtime/wire/shared.rs::NEXT_GENERATOR
Design: new stringly-typed @ crates/gateway-stt/src/realtime/wire/server.rs::EffectiveSession
Design: new stringly-typed @ crates/gateway-stt/src/realtime/wire/server.rs::ServerEvent
Design: new stringly-typed @ crates/gateway-stt/src/realtime/wire/server.rs::ConversationItem
Design: new stringly-typed @ crates/gateway-stt/src/realtime/wire/server.rs::WireError
Design: new oversized-unit @ crates/gateway-stt/src/realtime/wire/server.rs::ServerEvent::validate
Design: new pure-function @ crates/gateway-stt/src/realtime/wire/server.rs::validate_id deps: str
Design: new pure-function @ crates/gateway-stt/src/realtime/wire/server.rs::validate_optional_id deps: Option
Violates: A2 - not determinable from diff
Deferred: Realtime socket integration remains unwired
Plan: vibe/2026-09-05-2-generic-realtime-stt.md
Run architecture checks with the repository-supported Cargo release so ambient stable updates cannot break the pinned tools. Install the matching toolchain in continuous integration, force every architecture child process to use it, and fail closed when the Cargo release or a required tool is unavailable.

- `runCargo` passes a copied environment with `RUSTUP_TOOLCHAIN` fixed to `1.89`, and its injected `spawn` and `env` inputs make child selection testable.
- `requireCargoVersion` rejects other Cargo releases before module or public API checks run.
- `tools/check-stt-architecture.test.mjs` pins ambient Cargo 1.98 rejection, the child environment for both tools, and failures for an absent toolchain or command.
- `.github/workflows/ci.yml` installs Rust 1.89 and builds both pinned architecture tools with it while the job keeps stable as its default.

Design: new surface-growth @ tools/check-stt-architecture.mjs::requireCargoVersion deps: output boundary: pub
Design: new pure-function @ tools/check-stt-architecture.mjs::requireCargoVersion deps: output boundary: pub
Design: new surface-growth @ tools/check-stt-architecture.mjs::runCargo deps: args,env,root,spawn boundary: pub
Plan: vibe/2026-09-05-2-generic-realtime-stt.md
Remove extension trait imports that the test module does not use.

Plan: none
Build featureless Gateway binaries before Workshop compilation so Tauri can resolve its required external binary. Stage each binary under the target-qualified name for Windows and Linux, then remove it even when later checks fail.

- `TARGETS` centralizes the supported source and sidecar names for the Windows and Linux build targets.
- `.github/workflows/ci.yml` builds each featureless Gateway, stages it before Workshop checks, and removes it with an unconditional cleanup step.
- `stageGatewaySidecar` rejects unsupported targets, missing sources, non-file sources, and platform-name mismatches before it copies a binary.
- `tools/stage-gateway-sidecar.test.mjs` pins both target mappings, rejection paths, byte-preserving staging, and repeatable removal.

Design: new surface-growth @ tools/stage-gateway-sidecar.mjs::gatewayBinaryName deps: target boundary: pub
Design: new pure-function @ tools/stage-gateway-sidecar.mjs::gatewayBinaryName deps: target boundary: pub
Design: new surface-growth @ tools/stage-gateway-sidecar.mjs::gatewaySidecarName deps: target boundary: pub
Design: new pure-function @ tools/stage-gateway-sidecar.mjs::gatewaySidecarName deps: target boundary: pub
Design: new surface-growth @ tools/stage-gateway-sidecar.mjs::stageGatewaySidecar deps: root,source,target boundary: pub
Design: new surface-growth @ tools/stage-gateway-sidecar.mjs::removeGatewaySidecar deps: root,target boundary: pub
Plan: vibe/2026-09-05-2-generic-realtime-stt.md
Own each uncommitted audio stream, immutable configuration snapshot, interim epoch, and cleanup task within one isolated session. Reject excess sessions or canceled-task retention immediately, preserve retryable state on overload, and run pure ownership paths under Miri.

- `InputSnapshot` freezes the effective prompt and hypothesis option on the first successful append, while `UncommittedInput` owns audio conversion and take state until clear.
- `SessionRegistry` shares synchronized admission state across handles and keeps retiring sessions counted until every aborted interim task joins.
- `Session` receives its registration and optional engine at construction, owns bounded canceled-task joins, and rejects stale epochs before allocating event identifiers.
- `test_fixtures` adds a feature-gated public session surface for deterministic integration coverage.
- `realtime_session` pins exact session and canceled-join capacities, cancellation-safe retries, reset behavior, and immutable first-append state.
- `realtime` remains private and is not wired to a socket route.

Design: new value-object @ crates/gateway-stt/src/realtime/input.rs::InputSnapshot
Design: new constructor-injection @ crates/gateway-stt/src/realtime/input.rs::UncommittedInput::new
Design: new oversized-unit @ crates/gateway-stt/src/realtime/input.rs
Design: new shared-mutable-state @ crates/gateway-stt/src/realtime/registry.rs::SessionRegistry
Design: new oversized-unit @ crates/gateway-stt/src/realtime/registry.rs
Design: new constructor-injection @ crates/gateway-stt/src/realtime/session.rs::Session::new
Design: new oversized-unit @ crates/gateway-stt/src/realtime/session.rs
Design: new surface-growth @ crates/gateway-stt/src/test_fixtures.rs::RealtimeSessionRegistryFixture boundary: pub
Design: new surface-growth @ crates/gateway-stt/src/test_fixtures.rs::RealtimeInterimEpoch boundary: pub
Design: new surface-growth @ crates/gateway-stt/src/test_fixtures.rs::RealtimeInputSnapshotFixture boundary: pub
Design: new surface-growth @ crates/gateway-stt/src/test_fixtures.rs::RealtimeSessionFixture boundary: pub
Design: new oversized-unit @ crates/gateway-stt/src/test_fixtures.rs
Design: new clone-block @ crates/gateway-stt/tests/it/realtime_session.rs
Design: new oversized-unit @ crates/gateway-stt/tests/it/realtime_session.rs
Violates: A2 - credential ownership in crates/gateway-stt/src/realtime is not determinable from diff
Pending: N5 - compounds
Pending: N6 - compounds
Pending: N24 - compounds
Pending: N26 - compounds
Pending: N30 - compounds
Deferred: Realtime socket route integration remains unwired
Plan: vibe/2026-09-05-2-generic-realtime-stt.md
Give each committed transcription item its own immutable input snapshot, take state, finalization task, durable lineage, and terminal outcome. Reserve bounded result, hypothesis, terminal, and final-segment capacity before detaching input so overload stays retryable and items can finish out of order. Extend deterministic and interpreter coverage for exact capacities, isolation, cancellation, failure, and cleanup. Record native runner remediation as deferred work, synchronize later migration deadlines, and preserve the promoted architecture comparison reports.

- `CommittedItem` owns one sealed input, one take, one finalization task, and one terminal transition. `Session::commit` validates and reserves capacity before it invalidates the interim epoch or detaches input.
- `FinalPipeline` uses bounded message passing for accurate segments and completion. `SessionRegistry` keeps admission occupied until canceled interim and finalization tasks finish joining.
- `ResultMailbox` bounds ordinary results at 16 while reserving one replaceable hypothesis and one terminal slot per item. `MAX_COMMITTED_ITEMS_PER_SESSION` and `FINAL_SEGMENT_CAPACITY` enforce four-item and four-segment limits.
- `.github/workflows/stt-miri.yml` expands interpreter coverage to committed-item ownership and queue bounds. `module-ceilings.toml` removes the completed take migration and shifts later migration targets.
- `vibe/stt-field-comparison-and-adoption.md` and `vibe/agent-runtime-field-comparison-and-adoption.md` preserve the promoted field reports.
- `ci-native-rustup` records the self-hosted runner preflight as pending; the native job remains unchanged.

Design: new shared-mutable-state @ crates/gateway-stt/src/realtime/item.rs::CommittedItem
Design: new oversized-unit @ crates/gateway-stt/src/realtime/item.rs
Design: extends oversized-unit @ crates/gateway-stt/src/realtime/input.rs
Design: extends shared-mutable-state @ crates/gateway-stt/src/realtime/registry.rs::SessionRegistry
Design: extends oversized-unit @ crates/gateway-stt/src/realtime/registry.rs
Design: new oversized-unit @ crates/gateway-stt/src/realtime/result_mailbox.rs
Design: extends oversized-unit @ crates/gateway-stt/src/realtime/session.rs
Design: new oversized-unit @ crates/gateway-stt/src/realtime/session/items.rs
Design: new oversized-unit @ crates/gateway-stt/src/realtime/session/state.rs
Design: replaces message-passing @ crates/gateway-stt/src/take/finalization.rs::FinalPipeline was: crates/gateway-stt/src/take.rs::FinalPipeline
Design: new shared-mutable-state @ crates/gateway-stt/src/take/finalization.rs::FinalPipeline
Design: replaces shared-mutable-state @ crates/gateway-stt/src/take/state.rs::TakeState was: crates/gateway-stt/src/take.rs::TakeState
Design: new oversized-unit @ crates/gateway-stt/src/take/agreement.rs
Design: new oversized-unit @ crates/gateway-stt/src/take/finalization.rs
Design: new oversized-unit @ crates/gateway-stt/src/take/state.rs
Design: new surface-growth @ crates/gateway-stt/src/test_fixtures.rs::RealtimeCommitFixture boundary: pub
Design: extends surface-growth @ crates/gateway-stt/src/test_fixtures.rs::RealtimeSessionFixture boundary: pub
Design: extends oversized-unit @ crates/gateway-stt/src/test_fixtures.rs
Design: extends clone-block @ crates/gateway-stt/tests/it/realtime_session.rs
Design: extends oversized-unit @ crates/gateway-stt/tests/it/realtime_session.rs
Violates: A2 - credential ownership in crates/gateway-stt/src/realtime is not determinable from diff
Pending: N6 - compounds
Pending: N24 - compounds
Pending: N30 - compounds
Pending: N34 - compounds
Deferred: self-hosted native runner Rust preflight remains unimplemented
Plan: vibe/2026-09-05-2-generic-realtime-stt.md
Make the self-hosted native job use its provisioned stable Rust toolchain. Fail before caching when required binaries or the stable toolchain are absent, then expose the tool directory to later steps. Add source tests that preserve the hosted interpreter setup and reject installer regressions.

- `.github/workflows/stt-miri.yml` replaces the native toolchain installer with a preflight that resolves `rustup.exe` and `cargo.exe` under `$env:USERPROFILE`, disables automatic installation, and writes `$cargoBin` to `$env:GITHUB_PATH`.
- `Verify preinstalled stable Rust` lists installed toolchains, requires a stable entry, and invokes `$cargo` with `+stable`; each failed precondition throws a provisioning error.
- `tools/check-stt-native-workflow.test.mjs` pins preflight order and failure text, rejects installer actions in the native job, and confirms that `pure-stt-state` keeps `nightly-2026-09-05`.

Design: new hidden-dependency @ .github/workflows/stt-miri.yml boundary: persisted
Design: new temporal-coupling @ .github/workflows/stt-miri.yml boundary: persisted
Design: new oversized-unit @ tools/check-stt-native-workflow.test.mjs
Plan: vibe/2026-09-05-2-generic-realtime-stt.md
Unify speech lifecycle, model facts, and routes behind one cloneable service so each caller observes one complete generation. Stage verified artifacts and worker state before publication, preserve batch and temporary legacy behavior through service methods, and reduce the production root to six types.

- `SpeechService` owns preparation, staged replacement, publication, shutdown, status, model discovery, and route construction through one public facade.
- `GenerationState` stores the engine, physical names, guidance, backend, admission, and generation identifier in one lock-published snapshot shared by service clones.
- `module-ceilings.toml` fixes the public-root budget at six and removes completed migration targets for `api.rs` and `runtime.rs`.
- `batch::routes` owns the upload limit and OpenAI error envelopes while `authorize_stt_route` applies authentication and cancellation to every speech route.
- `scripted_service` and `service.rs` pin complete clone snapshots, physical model selection, temporary legacy capability, unload, and Gateway error envelopes.
- `Admission` has only an open state, and `unload` waits without a deadline for generation and engine references. Bounded quiescence remains absent.

Design: new encapsulated-invariant @ crates/gateway-stt/src/artifacts.rs::PreparedSpeech boundary: pub
Design: new oversized-unit @ crates/gateway-stt/src/artifacts.rs
Design: replaces oversized-unit @ crates/gateway-stt/src/batch.rs was: crates/gateway-stt/src/api.rs
Design: new oversized-unit @ crates/gateway-stt/src/batch/native_tests.rs
Design: new oversized-unit @ crates/gateway-stt/src/batch/tests.rs
Design: new parameter-object @ crates/gateway-stt/src/generation.rs::Generation
Design: replaces shared-mutable-state @ crates/gateway-stt/src/generation.rs::GenerationState was: crates/gateway-stt/src/runtime.rs::SttSlot
Design: new encapsulated-invariant @ crates/gateway-stt/src/generation.rs::SpeechReplacement boundary: pub
Design: replaces hidden-dependency @ crates/gateway-stt/src/generation.rs::unload deps: Option was: crates/gateway-stt/src/runtime.rs::unload_engine
Design: new oversized-unit @ crates/gateway-stt/src/generation.rs
Design: new newtype @ crates/gateway-stt/src/model.rs::SpeechModelInfo boundary: pub
Design: new facade @ crates/gateway-stt/src/service.rs::SpeechService boundary: pub
Design: new temporal-coupling @ crates/gateway-stt/src/service.rs::SpeechService::commit_replacement
Design: new oversized-unit @ crates/gateway-stt/src/service.rs
Design: new value-object @ crates/gateway-stt/src/status.rs::SpeechStatus boundary: pub
Design: replaces constructor-injection @ crates/gateway-stt/src/test_fixtures.rs::scripted_service deps: ScriptedModelFactory,u64,u64 was: crates/gateway-stt/src/runtime.rs::SttRuntime::from_scripted_engine
Design: new pure-function @ crates/gateway-stt/src/test_fixtures.rs::segment_ranges deps: &[f32] boundary: pub
Design: replaces surface-growth @ crates/gateway-stt/src/test_fixtures.rs::segment_ranges boundary: pub was: crates/gateway-stt/src/lib.rs::Segmenter
Design: extends facade @ crates/gateway-stt/src/lib.rs::test_fixtures
Design: extends oversized-unit @ crates/gateway-stt/src/test_fixtures.rs
Violates: A2 - credential ownership in SpeechService is not determinable from diff
Violates: A115 - control readiness during speech provisioning is not determinable from diff
Pending: N6 - compounds
Pending: N24 - compounds
Pending: N25 - compounds
Deferred: generation admission has no closed state
Deferred: generation unload has no finite wait bound
Plan: vibe/2026-09-05-2-generic-realtime-stt.md
Resolve the current Windows process identity as a canonical SID before restricting artifact cache access. This keeps private cache setup valid for interactive users and service accounts while failing closed on identity or ACL errors.

- `current_windows_sid` replaces profile environment names with one quoted CSV identity record from `whoami` and gives the validated SID to the ACL grant path.
- `target_step` and `removal_step` advance the legacy socket removal target by one execution position so the architecture ratchets preserve the same migration boundary.
- `parse_whoami_user_sid` delegates shape checks to `is_canonical_windows_sid`, accepts ordinary and service identities, rejects command failures and malformed or noncanonical output, and returns command error detail.
- `windows_sid_grant` renders the required SID principal prefix, while `artifact_store_enforces_private_windows_dacl` verifies that the current process retains write access after restriction.

Design: replaces hidden-dependency @ crates/gateway-local/src/artifacts/confine.rs::current_windows_sid deps: &Path was: crates/gateway-local/src/artifacts/confine.rs::current_windows_account
Design: new stringly-typed @ crates/gateway-local/src/artifacts/confine.rs
Design: new flag-parameter @ crates/gateway-local/src/artifacts/confine.rs::parse_whoami_user_sid deps: &[u8],&[u8],bool
Design: new pure-function @ crates/gateway-local/src/artifacts/confine.rs::parse_whoami_user_sid deps: &[u8],&[u8],bool
Design: new pure-function @ crates/gateway-local/src/artifacts/confine.rs::is_canonical_windows_sid deps: &str
Design: new pure-function @ crates/gateway-local/src/artifacts/confine.rs::windows_sid_grant deps: &str
Plan: vibe/2026-09-05-2-generic-realtime-stt.md
Serialize speech generation replacement and close admission while old requests and worker jobs drain. Explicit ownership counters and cancellation epochs prevent canceled requests from hiding live native work, while deadlines reopen the old snapshot and shutdown invalidates staged publication. Exact module ceilings and focused ownership tests enforce the lifecycle.

- `ReplacementCoordinator` owns one replacement lane, while `AdmissionGate` counts request and worker ownership under a fresh `SessionEpoch`.
- `GenerationLease` keeps each request attached to one complete snapshot, and `GenerationJob` keeps native work owned after request cancellation.
- `GenerationState` removes the active snapshot only after bounded drain, shuts down its workers, and builds the unpublished replacement afterward.
- `SttEngine::shutdown` now uses shared access and serializes worker cleanup inside `Transcriber`.
- `validate_module_ceiling` requires every manifest ceiling to equal the measured file size and rejects settled Gateway STT modules above 500 lines.
- `SpeechError` distinguishes a drain deadline from replacement invalidation by shutdown.

Design: extends surface-growth @ crates/gateway-stt-engine/src/engine.rs::SttEngine::shutdown boundary: pub
Design: extends shared-mutable-state @ crates/gateway-stt-engine/src/worker.rs::Transcriber
Design: new surface-growth @ crates/gateway-stt/src/artifacts.rs::SpeechError boundary: pub
Design: extends shared-mutable-state @ crates/gateway-stt/src/generation.rs::GenerationState
Design: extends encapsulated-invariant @ crates/gateway-stt/src/generation.rs::SpeechReplacement boundary: pub
Design: removes hidden-dependency @ crates/gateway-stt/src/generation.rs::unload deps: Option
Design: replaces parameter-object @ crates/gateway-stt/src/generation/snapshot.rs::Generation was: crates/gateway-stt/src/generation.rs::Generation
Design: new shared-parameter-cluster @ crates/gateway-stt/src/generation/snapshot.rs::Generation::from_factory
Design: new shared-mutable-state @ crates/gateway-stt/src/replacement.rs::ReplacementCoordinator
Design: new shared-mutable-state @ crates/gateway-stt/src/replacement.rs::SessionEpoch
Design: new shared-mutable-state @ crates/gateway-stt/src/replacement.rs::AdmissionGate
Design: new oversized-unit @ crates/gateway-stt/src/replacement.rs
Design: extends facade @ crates/gateway-stt/src/service.rs::SpeechService boundary: pub
Design: extends temporal-coupling @ crates/gateway-stt/src/service.rs::SpeechService::commit_replacement
Design: replaces constructor-injection @ crates/gateway-stt/src/test_fixtures/generation.rs::scripted_service deps: ScriptedModelFactory,u64,u64 boundary: pub was: crates/gateway-stt/src/test_fixtures.rs::scripted_service
Design: replaces pure-function @ crates/gateway-stt/src/test_fixtures/segment.rs::segment_ranges deps: &[f32] boundary: pub was: crates/gateway-stt/src/test_fixtures.rs::segment_ranges
Design: replaces surface-growth @ crates/gateway-stt/src/test_fixtures/segment.rs::segment_ranges boundary: pub was: crates/gateway-stt/src/test_fixtures.rs::segment_ranges
Design: replaces clone-block @ crates/gateway-stt/src/test_fixtures/native.rs was: crates/gateway-stt/src/test_fixtures.rs
Design: extends surface-growth @ crates/gateway-stt/src/test_fixtures.rs boundary: pub
Design: extends facade @ crates/gateway-stt/src/test_fixtures.rs boundary: pub
Design: new pure-function @ crates/gateway-stt/tests/it/architecture.rs::validate_module_ceiling deps: Option<usize>,usize,usize
Design: new pure-function @ crates/gateway-stt/tests/it/architecture.rs::calls_associated_method deps: &str,&str,&str
Design: new pure-function @ crates/gateway-stt/tests/it/architecture.rs::refcount_introspection deps: &str
Design: new oversized-unit @ crates/gateway-stt/tests/it/generation.rs::active_replacement_drains_request_and_job_before_unload_and_publication
Pending: N24 - compounds
Pending: N25 - compounds
Plan: vibe/2026-09-05-2-generic-realtime-stt.md
Repair the Workshop baseline ratchet while keeping the tests equivalent. Add inputs to RecordingObserver and use it for all observer mutex access. Shorten or remove helper comments.

- The staged change stays inside mod tests. It does not add or remove test cases or expected values.
Retire the custom speech socket, capability proxy, status channel, and browser fallback after canonical fixtures and independent Gateway, relay, and browser suites cover their behavior. Leave one authenticated Realtime transcription path beside batch transcription and remove the reverse Workshop dependency. Enforce the final graph, route surface, and zero-symbol state as permanent architecture gates.

- `gateway-stt` now depends on configuration, artifact, backend, engine, and progress crates only. `gateway` owns route mounting, the Whisper backend depends on the engine and FFI leaf, and `workshop-server` remains an independent authenticated relay with no temporary dependency exceptions.
- `SpeechService` merges only batch and Realtime routes. Gateway keeps `POST /v1/audio/transcriptions` and `WS /v1/realtime?intent=transcription`; Gateway and Workshop both return not found for `/stt` and `/stt/capability`, and Workshop fixes its upstream socket to Realtime.
- `legacy_stream.rs` policy, generation, origin, interim, final, fallback, segmentation, silence, and disconnect assertions map to the canonical wire contract and the mounted scheduler, hypothesis, completion, authority, privacy, and typed-error cases in `realtime_stt.rs`. The removed `take.rs` agreement and fallback cases map to the same producer-partition, skipped-range, divergent-final, and terminal-failure evidence.
- `routes/stt.rs` relay assertions map to the authenticated, same-origin, payload-opaque, control-frame, and close propagation cases in `realtime_relay.rs`. The boolean, malformed-body, and network cases in `stt-capability.mjs` map to removal of the probe, not-found route checks, boot-time unexpected-fetch rejection, and Realtime ready or unavailable cases in `agent-stt.mjs`; insertion, second-take, cleanup, and capture remain covered by `agent-stt-boot.mjs` and the sole `pcm16-capture` processor checks in `pcm-worklet.mjs`.
- `legacy_speech_seams_are_absent_from_production_sources` rejects the old Rust modules, connectors, headers, route factories, browser types, capability probe, and processor name. Its companion zero-symbol test injects every forbidden UI form, checks adversarial processor contexts, and proves current Realtime and browser-capture symbols remain accepted.

Design: removes surface-growth @ crates/gateway-stt/src/stt.rs boundary: wire
Design: removes shared-mutable-state @ crates/gateway-stt/src/generation.rs::Shared::changes
Design: removes flag-parameter @ crates/workshop-server/src/gateway/socket.rs::GatewayClient::connect_socket
Design: removes surface-growth @ crates/workshop-server/src/routes/stt.rs boundary: wire
Design: removes speculative-abstraction @ crates/workshop-server/src/serve.rs::RouteFactory
Design: removes surface-growth @ crates/workshop-server/src/lib.rs::spawn_with_routes boundary: pub
Design: removes surface-growth @ crates/workshop-server/ui/src/ui/stt.ts::sttCapability boundary: pub
Violates: A2 - credential ownership in crates/gateway-stt/src/service.rs::SpeechService is not determinable from diff
Pending: N36 - compounds
Plan: vibe/2026-09-05-2-generic-realtime-stt.md
Replace migration allowances with exact final architecture gates, isolate Gateway builds from Workshop tooling, and make the completed speech topology discoverable from maintained documentation. Record verified debt reduction and deterministic guide generation so later changes have explicit dependency, public-surface, and source-size baselines.

- `crates/gateway-stt/tests/it/architecture.rs` enforces exact workspace edges, exact public-root counts of 6, 7, 2, and 6, complete source manifests, and a 500-line maximum for every STT source module. `tools/check-stt-architecture.mjs` requires acyclic production module graphs and exact root counts for all four crates with pinned Cargo and analysis tools.
- `.github/workflows/ci.yml` builds Gateway after installing only the config UI dependencies, then runs scripted and Rust architecture checks in normal CI. `.github/workflows/stt-miri.yml` removes Workshop UI setup from the native speech lane.
- `crates/gateway-stt-engine/src/test_fixtures/tests.rs` separates 304 lines of deterministic worker tests from the fixture implementation so both modules satisfy the final ceiling without changing their assertions.
- `AGENTS.md` and `crates/workshop-server/AGENTS.md` correct build and ownership rules. Gateway, configuration, Workshop, and source-guide documentation now describe the generic Realtime route, exact bounds, discovery facts, and payload-opaque relay.
- `design/generic-realtime-stt.md` records the final ownership, dependency, wire, lifecycle, CI, and debt architecture. `design/generic-realtime-stt-acceptance.md` records passing gates, the before and after counts, refreshed generated Gateway and Workshop guides, and identical hashes for all nine generated artifacts on a clean second run.

Design: replaces oversized-unit @ crates/gateway-stt-engine/src/test_fixtures/tests.rs was: crates/gateway-stt-engine/src/test_fixtures.rs::tests
Design: new pure-function @ tools/check-stt-architecture.mjs::publicRootCount deps: crateName,source boundary: pub
Design: new surface-growth @ tools/check-stt-architecture.mjs::publicRootCount deps: crateName,source boundary: pub
Design: new pure-function @ tools/check-stt-architecture.mjs::requireExactPublicRootCount deps: actual,crateName,expected boundary: pub
Design: new surface-growth @ tools/check-stt-architecture.mjs::requireExactPublicRootCount deps: actual,crateName,expected boundary: pub
Design: new pure-function @ crates/gateway-stt/tests/it/architecture.rs::dependency_drift_message deps: &str
Design: extends pure-function @ crates/gateway-stt/tests/it/architecture.rs::validate_module_ceiling deps: usize,usize
Violates: A2 - credential ownership in crates/gateway-stt/tests/it/architecture.rs is not determinable from diff
Pending: N17 - compounds
Plan: vibe/2026-09-05-2-generic-realtime-stt.md
Bookend every serving file log with a versioned launch record and one terminal outcome. Keep the launch record first, place fatal termination after the complete error chain, and leave no-subscriber launches on their existing output paths.

- `main` emits terminal records only when `logging.is_some()` and shuts down the runtime afterward, so the final file record drains before process exit.
- `init_logging` emits `promptforge-gateway {} starting` immediately after subscriber installation and before `logging to {}`.
- `headless_serve_bookends_the_log_file` spawns the real executable, waits for its connection file, posts the shutdown route, waits for successful child exit, and asserts the first and last log lines.
- `a_fatal_boot_error_lands_in_the_log_with_its_chain` runs a failing child and asserts that the fatal terminal record follows the last `caused by:` record and remains last.
- `main` still returns before `init_logging` for help, version, diagnostics, and second-instance handoff. `init_logging` keeps both stdout-only branches without a file runtime, and `print_error_chain` remains the no-subscriber error fallback.

Design: new surface-growth @ crates/gateway/src/main.rs::main boundary: persisted
Design: new surface-growth @ crates/gateway/src/main.rs::init_logging boundary: persisted
Violates: A2 - not determinable from diff
Plan: vibe/2026-09-05-2-generic-realtime-stt.md
Supervise local sidecar Gateways and publish each validated replacement as one endpoint and credential generation. Wake heartbeat, progress, catalog, chat, proxy, and Realtime consumers only after the complete snapshot is live, while explicit LAN targets stay fixed. Preserve configured bearer identity across unchanged restarts and accept replacement identity by process or boot data, independent of port reuse. Complete all release, package, recovery, and operator acceptance gates.

- `GatewayBinding` centralizes the HTTP client, model client, endpoint, bearer, and generation in one immutable snapshot. Replacement builds the complete snapshot before atomic publication and consumer notification.
- `run_supervision` re-resolves the connection file, validates process image, health, and bearer acceptance, and launches the installed sibling under bounded backoff when no live local Gateway remains. New process or boot identity permits unchanged ports and keys, while configured key edits publish with their replacement.
- `composeTranscript` owns one separator only for standalone dictation at the logical document end. Hypotheses, completions, rollback, selected replacement, and producer-supplied whitespace keep consistent composition.
- `design/generic-realtime-stt-acceptance.md` records the complete release suite, native and architecture gates, generated-document hashes, installed package identities, recovery after more than 60 seconds, and final operator acceptance. Signing remains untested and deferred to release CI.

Design: new facade @ crates/workshop-server/src/gateway_binding.rs::GatewayBinding
Design: new parameter-object @ crates/workshop-server/src/gateway_binding.rs::GatewayBinding
Design: new shared-mutable-state @ crates/workshop-server/src/gateway_binding.rs::GatewayBinding
Design: new surface-growth @ crates/workshop-server/src/gateway_binding.rs::GatewayUpdater boundary: pub
Design: new surface-growth @ crates/workshop-server/src/gateway.rs::GatewayError::InvalidSidecar boundary: pub
Design: new surface-growth @ crates/workshop-server/src/serve.rs::ServerHandle::gateway_updater boundary: pub
Design: new surface-growth @ crates/workshop-server/src/lib.rs::fixtures::gateway_updater boundary: pub
Design: extends oversized-unit @ crates/workshop-server/src/session_agents/supervisor.rs::spawn deps: AgentSession,AgentSessions,GatewayBinding,SessionHost
Design: new shared-mutable-state @ crates/workshop/src/main.rs::GatewaySlot
Design: extends service-locator @ crates/workshop/src/main.rs::run
Design: new pure-function @ crates/workshop/src/gateway.rs::same_gateway_identity deps: &ConnectionFile,&ConnectionFile
Design: extends parallel-abstraction @ crates/workshop-server/ui/src/ui/realtime-stt.ts::Take
Design: extends oversized-unit @ crates/workshop-server/ui/src/ui/realtime-stt.ts::setupStt deps: RealtimeTranscriptionService,SpeechCaptureService,SttBlocker,SttElements,SttStatus boundary: pub
Design: extends surface-growth @ crates/workshop-server/ui/src/ui/stt.ts::SttInputTarget boundary: pub
Design: new pure-function @ crates/workshop-server/ui/test/agent-stt.mjs::producerHypothesis deps: itemId,revision,transcript
Design: new pure-function @ crates/workshop-server/ui/test/agent-stt.mjs::producerCommitted deps: itemId
Design: new pure-function @ crates/workshop-server/ui/test/agent-stt.mjs::producerCompletion deps: itemId,transcript
Pending: N57 - compounds
Plan: vibe/2026-09-05-2-generic-realtime-stt.md
Plan: vibe/2026-09-05-2-generic-realtime-stt.md
Split Gateway Realtime integration coverage into six concern-focused files while retaining shared support in the parent module. Preserve all 18 tests, including the ignored native case, with unchanged test bodies. Seed and activate the attributable debt-removal plan for the remaining work.

- `crates/gateway/tests/it/realtime_stt.rs` keeps shared fixtures and uses `include!` to assemble the authentication, protocol, lifecycle, recovery, overload, and canonical sequence coverage in one module scope.
- `vibe/2026-09-07-1-promptforge-debt.md` records the debt program and marks `Step 1` complete, while `vibe/ACTIVE` selects it for continued execution.

Design: new oversized-unit @ crates/gateway/tests/it/realtime_stt/authentication.rs
Design: new oversized-unit @ crates/gateway/tests/it/realtime_stt/authentication.rs::gateway_auth_origin_query_and_final_speech_surfaces_precede_upgrade
Design: new oversized-unit @ crates/gateway/tests/it/realtime_stt/canonical_sequence.rs
Design: replaces oversized-unit @ crates/gateway/tests/it/realtime_stt/canonical_sequence.rs::canonical_fixture_drives_hypothesis_completion_and_clear was: crates/gateway/tests/it/realtime_stt.rs::canonical_fixture_drives_hypothesis_completion_and_clear
Design: new oversized-unit @ crates/gateway/tests/it/realtime_stt/lifecycle.rs
Design: replaces oversized-unit @ crates/gateway/tests/it/realtime_stt/lifecycle.rs::interim_scheduler_enforces_cadence_minimum_silence_and_coalescing was: crates/gateway/tests/it/realtime_stt.rs::interim_scheduler_enforces_cadence_minimum_silence_and_coalescing
Design: new oversized-unit @ crates/gateway/tests/it/realtime_stt/overload.rs
Design: replaces oversized-unit @ crates/gateway/tests/it/realtime_stt/overload.rs::saturated_commit_preserves_the_canonical_input_for_retry was: crates/gateway/tests/it/realtime_stt.rs::saturated_commit_preserves_the_canonical_input_for_retry
Design: new oversized-unit @ crates/gateway/tests/it/realtime_stt/protocol.rs
Design: new oversized-unit @ crates/gateway/tests/it/realtime_stt/protocol.rs::mounted_route_drives_scripted_wire_ownership_errors_and_privacy
Design: new oversized-unit @ crates/gateway/tests/it/realtime_stt/protocol.rs::mounted_session_errors_keep_canonical_codes_parameters_and_correlation
Design: new oversized-unit @ crates/gateway/tests/it/realtime_stt/recovery.rs
Design: new oversized-unit @ crates/gateway/tests/it/realtime_stt/recovery.rs::admission_is_bounded_and_replacement_closes_with_1012
Plan: vibe/2026-09-07-1-promptforge-debt.md
Split Workshop chat and Realtime relay integration tests into concern-focused modules while each parent retains shared fixtures. Preserve all 11 chat tests and all 9 relay tests with unchanged test bodies.

- `crates/workshop-server/tests/it/chat_gate.rs` uses `include!` to assemble protocol, lifecycle, recovery, overload, and canonical sequence coverage.
- `crates/workshop-server/tests/it/realtime_relay.rs` uses `include!` to assemble authentication, protocol, lifecycle, recovery, overload, and canonical sequence coverage.
- `crates/workshop-server/tests/it/chat_gate.rs` and `crates/workshop-server/tests/it/realtime_relay.rs` add no persistent count or physical-line ceiling.

Design: new oversized-unit @ crates/workshop-server/tests/it/chat_gate/lifecycle.rs
Design: replaces oversized-unit @ crates/workshop-server/tests/it/chat_gate/lifecycle.rs::gate_catalog_replacement_during_acceptance_settles_the_turn_exactly_once was: crates/workshop-server/tests/it/chat_gate.rs::gate_catalog_replacement_during_acceptance_settles_the_turn_exactly_once
Design: new oversized-unit @ crates/workshop-server/tests/it/chat_gate/recovery.rs
Design: replaces oversized-unit @ crates/workshop-server/tests/it/chat_gate/recovery.rs::gate_restart_reloads_the_jsonl_and_resumes_waiting_for_input was: crates/workshop-server/tests/it/chat_gate.rs::gate_restart_reloads_the_jsonl_and_resumes_waiting_for_input
Design: replaces oversized-unit @ crates/workshop-server/tests/it/chat_gate/recovery.rs::gate_binding_loss_surfaces_one_error_and_recovers_after_selection was: crates/workshop-server/tests/it/chat_gate.rs::gate_binding_loss_surfaces_one_error_and_recovers_after_selection
Design: new oversized-unit @ crates/workshop-server/tests/it/realtime_relay/authentication.rs
Design: replaces oversized-unit @ crates/workshop-server/tests/it/realtime_relay/authentication.rs::realtime_relay_is_authenticated_fixed_and_payload_opaque boundary: wire was: crates/workshop-server/tests/it/realtime_relay.rs::realtime_relay_is_authenticated_fixed_and_payload_opaque
Pending: N49 - compounds
Pending: N50 - compounds
Plan: vibe/2026-09-07-1-promptforge-debt.md
Freeze the split integration suites behind exact source, include, size, and test-count contracts. Use syntax-aware Rust discovery so comments, literals, attributes, macros, and configuration gates cannot hide drift.

- `checkIntegrationTestCeilings` normalizes repository paths, requires exact suite and file coverage, verifies every direct `include!` exactly once, enforces physical-line ceilings, and checks exact test totals.
- `analyzeRust` tokenizes Rust syntax and fails closed on nested includes, generated tests, conditional tests, unsupported test attributes, and malformed delimiters or literals.
- `tools/integration-test-ceilings.json` records 18 Gateway Realtime tests, 11 Workshop chat tests, and 9 Workshop relay tests with ceilings for every entry and concern file.
- `tools/check-integration-test-ceilings.test.mjs` covers newline variants, path separators, comments, multiline attributes, configuration gates, macro generation, missing and extra files, include drift, ceiling overruns, and total drift.
- `.github/workflows/ci.yml` runs the adversarial driver tests and the repository gate before architecture tool installation.

Design: new pure-function @ tools/check-integration-test-ceilings.mjs::repoPath deps: value
Design: new surface-growth @ tools/check-integration-test-ceilings.mjs::repoPath deps: value boundary: pub
Design: new pure-function @ tools/check-integration-test-ceilings.mjs::physicalLineCount deps: source
Design: new surface-growth @ tools/check-integration-test-ceilings.mjs::physicalLineCount deps: source boundary: pub
Design: new oversized-unit @ tools/check-integration-test-ceilings.mjs::analyzeRust deps: label,source
Design: new oversized-unit @ tools/check-integration-test-ceilings.mjs::checkIntegrationTestCeilings deps: manifest,requiredSuites,root
Design: new surface-growth @ tools/check-integration-test-ceilings.mjs::checkIntegrationTestCeilings deps: manifest,requiredSuites,root boundary: pub
Plan: vibe/2026-09-07-1-promptforge-debt.md
Apply one immutable budget set to formatted records and later queue, wait, shutdown, segment, and retention work. Use fixed-capacity buffers through formatting and text redaction, validate the complete input as UTF-8 even after retention stops, and keep only valid prefixes with an explicit truncation marker.

- `LOG_LIMITS` centralizes six memory, latency, and disk budgets with compile-time relationships; only `max_formatted_record_bytes` takes effect in this change.
- `LogEventWriter` replaces growable event storage with `BoundedBytes`, scans retained and discarded input through `Utf8Validator`, rejects invalid or incomplete UTF-8, and marks valid truncation with `TRUNCATION_MARKER`.
- `RedactedLine` keeps every redaction buffer at the record capacity and preserves valid character boundaries when replacement text expands the result.
- `LossCounts` combines eviction, truncation, and rejection in one pressure summary; rejected records count as dropped while retained truncated records count as affected.
- `crates/gateway-logging/src/queue.rs` still reserves sequence before locking, blocks protected producers without a timeout, and reports loss only when empty; aggregate queued bytes, segment rotation, and broader redaction remain outside this change.

Deferred: Enforce aggregate queued bytes and admission ordering.
Deferred: Bound producer waits and shutdown.
Deferred: Rotate log segments under the aggregate retention budget.
Deferred: Expand structured and textual redaction coverage.
Plan: vibe/2026-09-07-1-promptforge-debt.md
Bind sequence assignment to successful admission and enforce record and byte ceilings under the same queue lock. Preserve priority eviction while fencing pressure summaries after all records admitted before the low-water transition, so repeated pressure episodes remain distinct and observable.

- `State` owns queued bytes, the next sequence, loss counts, and pending summaries under one mutex, while `QueueLimits` defines record and byte ceilings and their shared half-capacity low-water mark.
- `enqueue_after` rejects records larger than the byte budget, evicts eligible lower-priority records until both ceilings permit admission, and blocks producers when neither admission nor priority-safe eviction can proceed.
- `PendingSummary` fixes each closed loss episode after its admitted tail and before later records; a second pressure episode receives independent dropped, truncated, and rejected counts.
- `byte_blocked_producers_wake_after_drain_and_close` proves byte-blocked producers wake after capacity returns or admission closes.
- `crates/gateway-logging/src/queue.rs` retains unbounded producer waits; shutdown timeout handling remains outside this change.

Design: new oversized-unit @ crates/gateway-logging/src/queue.rs::byte_blocked_producers_wake_after_drain_and_close
Violates: A2 - credential ownership in gateway logging is not determinable from diff
Deferred: Producer wait and shutdown timeout handling remain outside this commit.
Plan: vibe/2026-09-07-1-promptforge-debt.md
Apply configured deadlines to protected producers and logger shutdown, including time spent acquiring the queue mutex. Preserve loss counts across close races, attempt an emergency diagnostic after timeout, and detach stalled workers while healthy sinks still drain, flush, and join.

- `LogQueue` registers active producers through `admission_gate` before mutex acquisition and keeps outstanding record, summary, and pressure counts in preallocated atomics.
- `LogWorker` replaces the unit owner with a single-field handle owner; `is_finished` supports bounded waiting and `join` consumes only a finished worker.
- `enqueue_after` starts the producer deadline before its admission hook, includes mutex and condition-variable waits, and records a timeout as rejected pressure.
- `close_locked` preaccounts `blocked_producers`; `close_accounted` prevents an awakened producer from reporting the same close loss twice.
- `shutdown_with_waiter` reserves up to `MAX_EMERGENCY_START_WAIT` for diagnostic startup, shares the remaining budget across queue closure and worker completion, and invokes `abandon` before it drops an unfinished owner.
- `shutdown` converts both `Joined` and `Detached` outcomes into a successful unit result; detachment loss reaches best-effort stderr through `write_emergency_diagnostic`.
- `complete_batch` runs only after `flush`; deterministic `StallPoint` tests block `Write`, `Flush`, queue closure, and the diagnostic helper while the healthy path still joins.
- `attempt_emergency_diagnostic` starts a detached stderr helper and waits only for its startup handshake; it adds no spool and does not await the diagnostic write.

Design: new shared-parameter-cluster @ crates/gateway-logging/src/queue.rs::LogQueue::new_for_test_with_wait
Design: new oversized-unit @ crates/gateway-logging/src/queue.rs::LogQueue::enqueue_after
Design: new flag-parameter @ crates/gateway-logging/src/queue.rs::LogQueue::complete_batch
Design: removes oversized-unit @ crates/gateway-logging/src/queue.rs::byte_blocked_producers_wake_after_drain_and_close
Design: new surface-growth @ crates/gateway-logging/src/runtime.rs::LogRuntime::shutdown boundary: pub
Design: new swallowed-exception @ crates/gateway-logging/src/runtime.rs::LogRuntime::shutdown boundary: pub
Design: new oversized-unit @ crates/gateway-logging/src/runtime.rs::assert_stalled_shutdown
Design: new newtype @ crates/gateway-logging/src/worker.rs::LogWorker
Violates: A2 - credential ownership in gateway logging is not determinable from diff
Plan: vibe/2026-09-07-1-promptforge-debt.md
Remove floating Rust selection and the fixed user-profile tool layout from native CI. Pin RUSTUP_TOOLCHAIN to 1.89.0, set RUSTUP_AUTO_INSTALL to zero, and validate cargo.exe and rustc.exe from PATH or PROMPTFORGE_RUST_1_89_0_BIN before Cargo caching.

- The versioned contract accepts only an absolute existing directory. It adds that directory to GITHUB_PATH only after both tools pass.
- The preflight rejects missing tools, command failures, unknown version output, and versions other than 1.89.0.
- tools/check-stt-native-workflow.test.mjs adds checks for the pinned MSRV contract and keeps Miri setup and fixture hashes unchanged.
Keep the STT architecture API gate deterministic across runners. Run standard cargo checks with 1.89.0, run cargo-public-api through nightly-2026-09-05, and select each crate by manifest and package name.

- Provision 1.89.0 and nightly-2026-09-05 in .github/workflows/ci.yml.
- Separate runCargo and runRustdocCargo so only public API inspection uses the pinned nightly.
- Make runPublicApi stop on a missing nightly or virtual manifest error without a fallback.
- Tests pin the command arguments, toolchain environment, exact package selection, and one-call failure behavior.
- This diff does not change public API snapshots, module ceilings, or legacy STT configuration parsing.
Bound each active and retained log segment and prune the oldest bytes before new writes exceed the aggregate disk budget. Preserve the current and numbered diagnostic names while reserving space for a truncation marker and one complete terminal record. Stage and sync replacements before transactional installation so rollback and restart recovery choose a complete old or committed chain.

- `SegmentedFile` owns active and retained byte counts, enforces both budgets before admission, and rotates only after flushing and syncing the active file.
- `rotate_files` creates durable staged copies, a preparation marker, rollback copies, and a durable commit marker. It restores old targets after an uncommitted failure and keeps committed targets during restart recovery.
- `open_log_file_with_limits` recovers interrupted work, compacts oversized legacy segments at valid text boundaries, prunes oldest retained data, and shifts the existing active log into the same numbered layout.
- `live_rotation_recovers_every_injected_filesystem_failure` and `restart_compaction_recovers_every_injected_filesystem_failure` inject each filesystem checkpoint, including remove-then-rename gaps, and prove recovery keeps one complete state.

Design: new oversized-unit @ crates/gateway-logging/src/worker.rs
Design: new pure-function @ crates/gateway-logging/src/worker.rs::valid_utf8_tail deps: &[u8]
Design: new pure-function @ crates/gateway-logging/src/worker.rs::artifact_path deps: &Path,&str
Design: new pure-function @ crates/gateway-logging/src/worker.rs::rotation_prepared_path deps: &Path
Design: new pure-function @ crates/gateway-logging/src/worker.rs::rotation_committed_path deps: &Path
Design: new pure-function @ crates/gateway-logging/src/worker.rs::rotation_targets deps: &Path,&[PathBuf]
Design: new pure-function @ crates/gateway-logging/src/worker.rs::crashing_fault deps: usize
Design: new oversized-unit @ crates/gateway-logging/src/worker.rs::live_rotation_recovers_every_injected_filesystem_failure
Design: new oversized-unit @ crates/gateway-logging/src/worker.rs::byte_boundaries_rotate_a_full_numbered_chain_without_splitting_utf8
Violates: A2 - credential ownership in gateway logging is not determinable from diff
Plan: vibe/2026-09-07-1-promptforge-debt.md
Suppress classified structured values before their formatters run, then scan bounded formatted text for secrets embedded in messages and dependency errors. Preserve default formatting for ordinary fields. Keep shutdown loss accounting exact across the admission boundary. Stage rotation through renames and empty durable markers so crash recovery does not exceed the aggregate disk budget.

- `LogWriter` uses `RedactingVisitor` for every structured field type and delegates unclassified values to `DefaultVisitor`.
- `redact_line_bounded` masks Basic and Bearer credentials, cookies, sensitive assignments, URLs, model paths, payloads, prompts, and nested error chains without emitting partial secrets at capacity boundaries.
- `admission_gate` tracks active producers and undelivered records in one atomic snapshot, and `abandonment_counts_admission_boundary_record_exactly_once` pins exact shutdown accounting.
- `rotate_files` moves segments into rollback positions instead of copying them. Crash-injection tests cover staging, committed cleanup, sparse chains, restart recovery, and directory-byte bounds.

Design: replaces oversized-unit @ crates/gateway-logging/src/queue.rs::LogQueue::enqueue_around was: crates/gateway-logging/src/queue.rs::LogQueue::enqueue_after
Design: new oversized-unit @ crates/gateway-logging/src/redact.rs
Design: new pure-function @ crates/gateway-logging/src/redact.rs::is_sensitive_field deps: &str
Design: new pure-function @ crates/gateway-logging/src/redact.rs::sensitive_component_alias deps: &str,&str
Design: new pure-function @ crates/gateway-logging/src/redact.rs::redact_line_bounded deps: &str,usize
Design: new pure-function @ crates/gateway-logging/src/redact.rs::redact_line deps: &str
Design: new pure-function @ crates/gateway-logging/src/redact.rs::find_ascii deps: &str,&str,usize
Design: new oversized-unit @ crates/gateway-logging/src/redact.rs::next_sensitive_span
Design: new pure-function @ crates/gateway-logging/src/redact.rs::next_sensitive_span deps: &str,usize
Design: new pure-function @ crates/gateway-logging/src/redact.rs::starts_ascii deps: &str,&str,usize
Design: new pure-function @ crates/gateway-logging/src/redact.rs::assignment_span_at deps: &str,&str,usize
Design: new pure-function @ crates/gateway-logging/src/redact.rs::quoted_value_end deps: &[u8],u8,usize
Design: new pure-function @ crates/gateway-logging/src/redact.rs::url_span_at deps: &str,usize
Design: new pure-function @ crates/gateway-logging/src/redact.rs::local_path_span_at deps: &str,usize
Design: new pure-function @ crates/gateway-logging/src/redact.rs::sensitive_token_end deps: &str,usize
Design: extends oversized-unit @ crates/gateway-logging/src/worker.rs
Design: new pure-function @ crates/gateway-logging/src/worker.rs::rotation_staged_path deps: &Path
Design: extends pure-function @ crates/gateway-logging/src/worker.rs::rotation_prepared_path deps: &Path,u8
Design: new pure-function @ crates/gateway-logging/src/worker.rs::legacy_rotation_prepared_path deps: &Path
Design: new pure-function @ crates/gateway-logging/src/worker.rs::rotation_source_for deps: &Path,&[PathBuf],usize
Design: extends oversized-unit @ crates/gateway-logging/src/worker.rs::live_rotation_recovers_every_injected_filesystem_failure
Design: new oversized-unit @ crates/gateway-logging/src/writer.rs
Design: new surface-growth @ crates/gateway-logging/src/writer.rs::LogWriter boundary: pub
Violates: A2 - credential ownership in gateway logging is not determinable from diff
Pending: N62 - compounds
Pending: N68 - compounds
Plan: vibe/2026-09-07-1-promptforge-debt.md
Make configuration version 2 accept only canonical top-level speech tuning and reject legacy or mixed forms. Remove both parser and browser rewrites so obsolete input cannot cross either persistence boundary. Keep the installed canonical configuration byte-identical, as confirmed by matching read-only SHA-256 checks without exposing its contents.

- `migrate_legacy_stt` removes the Rust parser shim, and `canonicalizeStt` removes the TypeScript browser shim in the same change. `ConfigStore` now retains API configuration objects as received and clones pending data without normalization.
- `rejects_legacy_stt_section` and `rejects_canonical_and_legacy_stt_sections_together` require unknown-field parse failures, while canonical parser, serializer, and editor tests preserve the supported shape. `check_removed_workshop_stt_claims` rejects guide text that presents the removed section as usable.
- `guide/src/gateway/05-speech.md` and the related source, README, and generated guide updates state the version 2 canonical-only contract.
- `gateway.toml` remains unchanged and contains no legacy section according to the before-and-after read-only verification.

Design: removes shim @ crates/gateway-config/src/config/imp.rs::migrate_legacy_stt deps: &mut toml::Value boundary: persisted
Design: removes stringly-typed @ crates/gateway-config/src/config/imp.rs::migrate_legacy_stt deps: &mut toml::Value boundary: persisted
Design: removes shim @ crates/gateway-config-ui/ui/src/services/config-store.ts::canonicalizeStt deps: EntryData boundary: persisted
Design: removes stringly-typed @ crates/gateway-config-ui/ui/src/services/config-store.ts::canonicalizeStt deps: EntryData boundary: persisted
Plan: vibe/2026-09-07-1-promptforge-debt.md
Route native speech test assets through one feature-gated resolver while each caller keeps its own fallback root. Process environment overrides take precedence, and missing assets fail with a diagnostic that names the resolved path. Default builds do not expose the resolver.

- `require_fixture` accepts `&str`, `&Path`, and `&str`, returns `PathBuf`, and replaces resolver copies in five caller roots: `prompt.rs`, `native_whisper.rs`, `test_fixtures/native.rs`, `tests/common/mod.rs`, and `realtime_stt.rs`.
- `gateway-stt-engine` exposes the resolver only through `test-fixtures`; direct development dependencies wire the backend and Gateway test targets to it, and the Gateway dependency policy records the test-only edge.
- `PROMPTFORGE_WHISPER_LIBRARY`, `PROMPTFORGE_WHISPER_MODEL`, and `PROMPTFORGE_WHISPER_AUDIO` remain caller-selected environment overrides over explicit backend or workspace-local roots.
- `feature_boundary.rs` proves caller fallback selection, environment precedence, resolved-path diagnostics, and default feature absence with isolated consumer checks.
- `module-ceilings.toml` adds the 24-line engine resolver and raises the measured backend prompt, engine fixture root, and service fixture ceilings. `integration-test-ceilings.json` raises the Gateway Realtime root from 659 to 669 lines and its test total from 18 to 19.
- `feature_boundary.rs` adds a 179-line integration test file outside the source module ceilings.

Design: extends feature-flag @ crates/gateway-stt-engine/Cargo.toml::test-fixtures
Design: extends surface-growth @ crates/gateway-stt-engine/src/test_fixtures.rs::native boundary: pub
Design: new hidden-dependency @ crates/gateway-stt-engine/src/test_fixtures/native.rs::require_fixture deps: &Path,&str,&str boundary: pub
Design: new stringly-typed @ crates/gateway-stt-engine/src/test_fixtures/native.rs::require_fixture deps: &Path,&str,&str boundary: pub
Design: new pure-function @ crates/gateway-stt-backend-whisper/src/prompt.rs::native_fixture_root
Design: new pure-function @ crates/gateway-stt/src/test_fixtures/native.rs::native_fixture_root
Design: new pure-function @ crates/gateway-stt/tests/common/mod.rs::native_fixture_root
Design: new pure-function @ crates/gateway/tests/it/realtime_stt.rs::native_fixture_root
Design: removes clone-block @ crates/gateway-stt/src/test_fixtures/native.rs::require_fixture deps: &str,&str
Design: removes clone-block @ crates/gateway-stt/tests/common/mod.rs::require_fixture deps: &str,&str
Design: new oversized-unit @ crates/gateway-stt-engine/tests/feature_boundary.rs
Violates: A2 - credential ownership in crates/gateway-stt/tests/it/architecture.rs is not determinable from diff
Pending: N17 - compounds
Pending: N19 - compounds
Pending: N20 - compounds
Plan: vibe/2026-09-07-1-promptforge-debt.md
Freeze both ordinary and test-enabled speech fixture surfaces before later contraction. Compare generated public APIs with exact snapshots and root-count records so drift or unusable tool output stops the architecture gate.

- `crates/gateway-stt/public-api-default.txt` and `crates/gateway-stt-engine/public-api-default.txt` record the exact default surfaces used to prove fixture symbols stay absent.
- `crates/gateway-stt/public-api-test-fixtures.txt` and `crates/gateway-stt-engine/public-api-test-fixtures.txt` record the exact surfaces exposed with `test-fixtures`.
- `runPublicApi` keeps `cargo +nightly-2026-09-05 public-api` pinned to each crate manifest and package, and enables only `test-fixtures` for feature snapshots.
- `test_fixture_public_root_count` records exact feature-enabled root counts of 7 for `gateway-stt` and 8 for `gateway-stt-engine` in both ceiling manifests and the Rust architecture gate.
- `canonicalPublicApi` normalizes CRLF to LF, rejects bare CR, empty output, missing final newlines, malformed records, additions, removals, missing tools, and failed tool invocations.
- `public-api-test-fixtures.txt` establishes a measured baseline only. This change does not narrow existing fixture controls or alter production API definitions.

Design: new global-state @ tools/check-stt-architecture.mjs::FIXTURE_STT_CRATES
Design: new pure-function @ tools/check-stt-architecture.mjs::testFixturePublicRootCount deps: crateName,source boundary: pub
Design: new surface-growth @ tools/check-stt-architecture.mjs::testFixturePublicRootCount deps: crateName,source boundary: pub
Design: new pure-function @ tools/check-stt-architecture.mjs::canonicalPublicApi deps: crateName,output
Design: new pure-function @ tools/check-stt-architecture.mjs::requireExactPublicApi deps: actual,crateName,expected boundary: pub
Design: new surface-growth @ tools/check-stt-architecture.mjs::requireExactPublicApi deps: actual,crateName,expected boundary: pub
Design: new pure-function @ tools/check-stt-architecture.mjs::requireNoFixtureApi deps: crateName,expected,output boundary: pub
Design: new surface-growth @ tools/check-stt-architecture.mjs::requireNoFixtureApi deps: crateName,expected,output boundary: pub
Design: new surface-growth @ tools/check-stt-architecture.mjs::runPublicApi deps: crateName,env,features,root,spawn boundary: pub
Design: new oversized-unit @ tools/check-stt-architecture.mjs::main
Design: new oversized-unit @ crates/gateway-stt/public-api-test-fixtures.txt
Design: new oversized-unit @ crates/gateway-stt-engine/public-api-test-fixtures.txt
Design: new pure-function @ crates/gateway-stt/tests/it/architecture.rs::expected_test_fixture_public_root_count deps: &str
Violates: A2 - credential ownership in crates/gateway-stt/tests/it/architecture.rs is not determinable from diff
Pending: N17 - compounds
Plan: vibe/2026-09-07-1-promptforge-debt.md
Contract feature-gated speech fixtures around bounded scenario operations so consumers no longer coordinate raw synchronization phases. Scoped decode and construction flows preserve behavior and guarantee release and joined cleanup after completion, cancellation, timeout, or panic.

- `ScriptedDecoder::with_next_decode_blocked` and `ScriptedModelFactory::with_construction_blocked` replace six public park, wait, and release methods with two bounded operations. The exact fixture snapshots contract from 101 to 97 lines and from 129 to 127 lines.
- `scenarios`, `scenario_cleanup`, `scheduling.rs`, and `capacity.rs` split fixture mechanics and integration coverage through one-way parent-to-child wiring. Updated ratchets lower the parent fixture and Realtime suite ceilings and record every new boundary.
- `RealtimeSessionFixture::accept_interim_across_clear` owns epoch creation, clear, and stale acceptance, while `spawn_interim` stops returning the raw epoch.
- `gateway-stt-engine` cleanup tests, `gateway-stt` generation and session tests, and Gateway provisioning and Realtime tests use the scoped operations. Cleanup coverage proves normal, canceled, timed-out, and panicked scenarios release blocked work and permit deterministic follow-up construction or decoding.

Design: removes temporal-coupling @ crates/gateway-stt-engine/src/test_fixtures.rs::ScriptedDecoder
Design: replaces shared-mutable-state @ crates/gateway-stt-engine/src/test_fixtures/scenarios.rs::ScriptedDecoder was: crates/gateway-stt-engine/src/test_fixtures.rs::ScriptedDecoder
Design: replaces oversized-unit @ crates/gateway-stt-engine/src/test_fixtures/scenarios.rs::ScriptedDecoder was: crates/gateway-stt-engine/src/test_fixtures.rs::ScriptedDecoder
Design: new oversized-unit @ crates/gateway-stt-engine/src/test_fixtures/tests/scenario_cleanup/construction.rs
Design: new oversized-unit @ crates/gateway-stt-engine/src/test_fixtures/tests/scenario_cleanup/decode.rs
Design: removes surface-growth @ crates/gateway-stt/src/test_fixtures.rs::RealtimeInterimEpoch boundary: pub
Design: replaces oversized-unit @ crates/gateway/tests/it/realtime_stt/capacity.rs::saturated_commit_preserves_the_canonical_input_for_retry was: crates/gateway/tests/it/realtime_stt/overload.rs::saturated_commit_preserves_the_canonical_input_for_retry
Design: replaces oversized-unit @ crates/gateway/tests/it/realtime_stt/scheduling.rs::interim_scheduler_enforces_cadence_minimum_silence_and_coalescing was: crates/gateway/tests/it/realtime_stt/lifecycle.rs::interim_scheduler_enforces_cadence_minimum_silence_and_coalescing
Violates: A2 - credential ownership in Gateway speech fixture changes is not determinable from diff
Plan: vibe/2026-09-07-1-promptforge-debt.md
Restore dead-code diagnostics across default, feature-enabled, unit-test, Miri, and featureless builds. Assign configuration-specific state to its build owner, enforce the policy recursively, and deny production warnings in continuous integration.

- `#[cfg(any(test, feature = "test-fixtures"))]` assigns fixture-visible fields, variants, imports, and inspection methods to test builds. `#[cfg(test)]` limits unit-test helpers, and `take` keeps one reasoned item-level allowance for retirement ownership.
- `requireNoBroadDeadCodeAllowances` recursively scans all Rust sources under the speech crate. `maskRustCommentsAndLiterals` excludes inert text before `broadDeadCodeAllowances` rejects crate and module suppressions.
- `crates/gateway-stt/src/lib.rs` removes the module-wide allowances from `audio` and `realtime`.
- `cargo check --locked -p gateway-stt --lib` runs with `RUSTFLAGS` set to deny warnings before the existing architecture test.
- `module-ceilings.toml` banks current physical line counts for each touched speech module.

Design: new shotgun-surgery @ crates/gateway-stt/src
Design: new oversized-unit @ tools/check-stt-architecture.mjs::maskRustCommentsAndLiterals deps: source
Violates: A2 - credential ownership in crates/gateway-stt/src/realtime is not determinable from diff
Pending: N30 - compounds
Plan: vibe/2026-09-07-1-promptforge-debt.md
Require the native job to validate preinstalled Rust 1.89.0 before it can publish a tool directory or use the cache. Support direct tools without rustup, and resolve rustup only after cargo identifies a proxy layout.

- `.github/workflows/stt-miri.yml` discovers `cargo.exe` and `rustc.exe` from `PROMPTFORGE_RUST_1_89_0_BIN` or `PATH`. It sets `RUSTUP_TOOLCHAIN` to `1.89`, keeps `RUSTUP_AUTO_INSTALL` at `"0"`, and requires exact tool version `1.89.0`.
- `Test-RustupProxy` probes `cargo` with `+$env:RUSTUP_TOOLCHAIN`. Direct layouts skip `rustup`, while proxy layouts resolve it conditionally and require matching `SHA256` hashes for the selected executables.
- `tools/check-stt-native-workflow.test.mjs` executes the extracted preflight against direct contract and `PATH` proxy fixtures. It asserts that version validation finishes before cache use and that all native Whisper work stays on `[self-hosted, windows, cuda]`.
- `.github/workflows/stt-miri.yml` adds no Rust installer and leaves the hosted Miri, native fixture source and hash, and native Whisper command pins unchanged.

Plan: vibe/2026-09-07-1-promptforge-debt.md
Give each process a stable random namespace for profile preparation files so reused process identifiers do not collide with crash residue. Retry exclusive creation within a fixed budget, preserve foreign residue, and report the exhausted target and final candidate. Transfer temporary-path ownership on rename so rollback and commit cleanup cannot remove a later owner's file. Keep persisted configuration bytes and the configuration format unchanged.

- `PERSISTENCE_NAMES` combines the process ID, a lazily initialized full-width random nonce, and an atomic per-process sequence. The nonce is formatted as 32 lowercase hexadecimal digits.
- `create_prepared` retries only `AlreadyExists` collisions for `PREPARED_CREATE_ATTEMPTS` and returns other I/O errors immediately. Exhaustion preserves every colliding path and reports the target, attempt count, final candidate, and source error through `PreparedCreateExhausted`.
- `PreparedFile` stores its owned temporary path in `Option<PathBuf>`. `Drop` removes only a still-owned rollback path, while `commit` clears ownership after a successful rename.
- `config_write.rs` leaves the config payload and destination schema unchanged. Tests cover deterministic collisions, PID reuse, bounded exhaustion, foreign residue preservation, rollback cleanup, and post-commit path reuse.

Design: extends global-state @ crates/gateway/src/config_write.rs::PERSISTENCE_NAMES
Design: extends encapsulated-invariant @ crates/gateway/src/config_write.rs::PreparedFile boundary: persisted
Design: hidden-dependency -> pure-function @ crates/gateway/src/config_write.rs::persistence_temporary deps: &Path,u128,u32,u64
Design: extends oversized-unit @ crates/gateway/src/config_write.rs
Violates: A116 - PreparedFile publication consistency with live state is not determinable from diff
Violates: A117 - PreparedFile routing availability during switch preparation is not determinable from diff
Plan: vibe/2026-09-07-1-promptforge-debt.md
Move profile target resolution, persistence preparation, cutover, and recovery behind private transaction phases. Each phase owns cancellation, cutover locking, and the prior runtime state needed for rollback while root orchestration stages and commits only a completed cutover. Preserve persistence, cancellation, and runtime behavior while leaving terminal outcome types for the next transaction slice.

- `profile_switch.rs` now owns target resolution, prepared files, artifact download, cutover, prior-state capture, and restoration. `config_write.rs` returns to request-boundary work, and `lib.rs` drops the moved helpers.
- `PreparedPhase` consumes itself through `cut_over` to create `CutoverPhase`. `PriorRuntimeSnapshot` travels with that value, and `run_switch_phases` receives one phase object instead of the preparation parameter cluster.
- `prepare` retains cancellation checks around target preparation, download, and persistence. `cut_over` retains `state.switch` locking, inference drain behavior, conditional download ordering, and rollback escalation.
- `PreparedPersistence` retains atomic file replacement, determinate and indeterminate failure classification, directory sync, matching-shadow cleanup, and owned temporary cleanup.
- `preparation_produces_a_prepared_phase_without_publishing_target` pins pre-cutover routing. `prepared_phase_transitions_once_to_cutover_with_prior_snapshot` pins interim publication and prior-runtime capture.
- `into_terminal_parts` returns ownership to the existing terminal path. Staged, committed, rolled-back, indeterminate, and terminal transition values remain deferred.

Design: replaces global-state @ crates/gateway/src/profile_switch.rs::PERSISTENCE_NAMES was: crates/gateway/src/config_write.rs::PERSISTENCE_NAMES
Design: replaces encapsulated-invariant @ crates/gateway/src/profile_switch.rs::PreparedFile boundary: persisted was: crates/gateway/src/config_write.rs::PreparedFile
Design: replaces pure-function @ crates/gateway/src/profile_switch.rs::persistence_temporary deps: &Path,u128,u32,u64 was: crates/gateway/src/config_write.rs::persistence_temporary
Design: replaces encapsulated-invariant @ crates/gateway/src/profile_switch.rs::PreparedPersistence boundary: persisted was: crates/gateway/src/lib.rs::PreparedPersistence
Design: replaces parameter-object @ crates/gateway/src/profile_switch.rs::PriorRuntimeSnapshot was: crates/gateway/src/lib.rs::CutoverState
Design: new encapsulated-invariant @ crates/gateway/src/profile_switch.rs::PreparedPhase
Design: new encapsulated-invariant @ crates/gateway/src/profile_switch.rs::CutoverPhase
Design: removes shared-parameter-cluster @ crates/gateway/src/lib.rs::prepare_cutover deps: &AppState,&ProfileName,&ProgressTree,&SwitchTarget,&tokio_util::sync::CancellationToken,StatePersistence,StopSet
Design: shared-parameter-cluster -> parameter-object @ crates/gateway/src/lib.rs::run_switch_phases deps: &AppState,&ProfileName,profile_switch::PreparedPhase
Design: replaces shared-parameter-cluster @ crates/gateway/src/profile_switch.rs::restore_or_shutdown deps: &AppState,&CancellationToken,GatewayError,PriorRuntimeSnapshot was: crates/gateway/src/lib.rs::restore_or_shutdown
Design: new oversized-unit @ crates/gateway/src/profile_switch.rs
Deferred: staged and terminal outcome values remain in run_switch_phases and commit_switch
Plan: vibe/2026-09-07-1-promptforge-debt.md
Move staging, commit, rollback, and fatal shutdown behind consuming transaction phases, and reduce the Gateway root to one delegation. Preserve cancellation gates and switch and publication lock order while persistence still precedes atomic live-state publication. Add direct terminal coverage and keep the outward switch result stable with or without optional runtimes.

- `StagedPhase`, `CommitTail`, `PublicationPhase`, and `TerminalPhase` consume success state in order. `RollbackOwner` owns staged rollback and restoration of `PriorRuntimeSnapshot`, while `IndeterminatePhase` owns controlled shutdown when state cannot be proven.
- `run_switch_with_config` delegates to `profile_switch::run`. `StagedPhase::commit` checks cancellation around `switch` and `apply`, restores determinate failures, and routes indeterminate staging, persistence, rollback, and speech publication through `request_fatal_shutdown`.
- `featureless_cancellation_stops_persistence_and_publication` uses `Spawn` instead of waiting for the absent `starting-models` leaf. `featureless_profile_switch_commits_the_complete_target`, `indeterminate_staging_timeout_requests_shutdown_without_persisting`, and `failed_speech_publication_is_indeterminate_after_persistence` drive terminal outcomes through the root entry point.
- `web_search`, `fake_brave`, and `gateway_with_web_search` share the `web-search` boundary so featureless tests compile without changing feature-enabled coverage.

Design: new facade @ crates/gateway/src/profile_switch.rs::run deps: &AppState,&CancellationToken,Option<Config>,ProfileName,ProgressTree,impl FnOnce() -> StatePersistence
Design: new encapsulated-invariant @ crates/gateway/src/profile_switch.rs::StagedPhase
Design: new encapsulated-invariant @ crates/gateway/src/profile_switch.rs::PublicationPhase
Design: new parameter-object @ crates/gateway/src/profile_switch.rs::RollbackOwner
Design: removes parameter-object @ crates/gateway/src/lib.rs::run_switch_phases deps: &AppState,&ProfileName,profile_switch::PreparedPhase
Design: removes shared-parameter-cluster @ crates/gateway/src/lib.rs::commit_switch deps: &AppState,&ProfileName,&tokio_util::sync::CancellationToken,PreparedPersistence,RuntimeReplacement,SwitchTarget
Design: removes oversized-unit @ crates/gateway/src/lib.rs::commit_switch
Design: removes shared-parameter-cluster @ crates/gateway/src/lib.rs::request_fatal_shutdown deps: &'static str,&AppState,&tokio_util::sync::CancellationToken,GatewayError
Design: removes shared-parameter-cluster @ crates/gateway/src/lib.rs::rollback_commit_failure deps: &AppState,&tokio_util::sync::CancellationToken,GatewayError,RuntimeReplacement
Design: removes shared-parameter-cluster @ crates/gateway/src/profile_switch.rs::restore_or_shutdown deps: &AppState,&CancellationToken,GatewayError,PriorRuntimeSnapshot
Design: extends oversized-unit @ crates/gateway/src/profile_switch.rs
Violates: A2 - credential ownership in crates/gateway/src/profile_switch.rs is not determinable from diff
Plan: vibe/2026-09-07-1-promptforge-debt.md
Route every Realtime server frame through one pure decoder before production dispatch. Enforce exact event shapes and semantic constraints while preserving isolated delta fallback across reconnects.

- `RealtimeEvent` and `decodeRealtimeEvent` define the supported discriminated union and reject unknown types, extra or missing fields, malformed nullable values, invalid identifiers and indices, unsafe revisions, inconsistent transcript partitions, reversed audio spans, and invalid duration usage.
- `RealtimeTranscriptionService` dispatches only validated events and sends malformed server values through the recoverable session error path.
- `realtime-wire-fixtures.mjs` mutates every canonical field and replays every canonical sequence through the production decoder. `stt-stream.mjs` pins production rejection, reconnect reset, per-item delta assembly, and hypothesis takeover.

Design: new surface-growth @ crates/workshop-server/ui/src/services/realtime-event-decoder.ts::RealtimeEvent boundary: pub
Design: new pure-function @ crates/workshop-server/ui/src/services/realtime-event-decoder.ts::decodeRealtimeEvent deps: unknown boundary: pub
Design: new dispatch-on-tag @ crates/workshop-server/ui/src/services/realtime-event-decoder.ts::decodeRealtimeEvent deps: unknown boundary: pub
Design: new oversized-unit @ crates/workshop-server/ui/src/services/realtime-event-decoder.ts::decodeRealtimeEvent deps: unknown boundary: pub
Design: extends surface-growth @ crates/workshop-server/ui/src/services/realtime-transcription.ts boundary: pub
Design: extends dispatch-on-tag @ crates/workshop-server/ui/src/services/realtime-transcription.ts::handleMessage
Violates: A96 - bounded third-party model content in crates/workshop-server/ui/src/services/realtime-event-decoder.ts is not determinable from diff
Pending: N53 - compounds
Pending: N55 - compounds
Plan: vibe/2026-09-07-1-promptforge-debt.md
Capture each take's immutable insertion anchor, rollback text, and separator inside its input target after microphone startup succeeds. This aligns editable startup races with the state that becomes locked while preserving insertion and rollback behavior across both editor representations.

- `SttInsertionContext` makes the selected range, original text, and composition prefix readonly. `SttInputTarget.insertionContext` replaces separate selection, document-end, and range-reading operations so textarea and ProseMirror targets own whitespace policy.
- `setupStt` captures the context after `capture.start` succeeds and directly before it registers the take and locks the input. Delayed-edit tests prove both targets insert at the post-start selection, restore that edited text on rollback, and retain the captured separator even if content changes.
- `crates/workshop-server/ui/src/ui/realtime-stt.ts` retains the current take lifecycle. This change adds no registry and changes no styles, markup, or visual behavior.

Violates: A96 - bounded third-party model content in setupStt is not determinable from diff
Pending: N59 - compounds
Plan: vibe/2026-09-07-1-promptforge-debt.md
Model each dictation take as immutable state reduced from typed user, capture, wire, connection, and decoded server inputs. Return typed editor, capture, status, and wire effects so transitions perform no external work. Leave production integration unchanged.

- `reduceTakeRegistry` clones all registry collections before each transition and returns the next state with its effects.
- `take-registry.ts` splits reduction, event handling, state helpers, and type definitions into an acyclic four-module graph whose files each remain below 500 lines.
- `RegistryTake` preserves both captured range endpoints, shifts later owned ranges by the exact replacement delta, and keeps completion text authoritative.
- `captureStopped` releases only the take named by `stoppingTakeId`, while request identifiers correlate wire results and server errors with their owner.
- `CommitExpectation` retains FIFO acknowledgment tombstones so discarded or duplicate commits cannot claim a later take.
- `take-registry.mjs` covers overlap, rollback, reconnect, spacing, completion authority, selection replacement, typed correlation, and immutable-state invariants. `take-registry-regressions.mjs` pins captured range width, tombstone consumption, and stale or duplicate capture completion.
- `take-registry.ts` has no production consumer in this change. Only the new tests import its reducer entry points.

Design: new pure-function @ crates/workshop-server/ui/src/ui/take-registry.ts::createTakeRegistry
Design: new pure-function @ crates/workshop-server/ui/src/ui/take-registry.ts::reduceTakeRegistry deps: TakeRegistry,TakeRegistryInput
Design: new dispatch-on-tag @ crates/workshop-server/ui/src/ui/take-registry.ts::reduceTakeRegistry deps: TakeRegistry,TakeRegistryInput
Design: new pure-function @ crates/workshop-server/ui/src/ui/take-registry-state.ts::cloneRegistry deps: TakeRegistry
Design: new pure-function @ crates/workshop-server/ui/src/ui/take-registry-state.ts::composeTranscript deps: RegistryTake,string
Design: new dispatch-on-tag @ crates/workshop-server/ui/src/ui/take-registry-events.ts::serverEvent deps: RealtimeEvent,Reduction
Deferred: production callbacks do not consume TakeRegistry
Plan: vibe/2026-09-07-1-promptforge-debt.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants